home *** CD-ROM | disk | FTP | other *** search
/ C/C++ Users Journal 1990 - 1995 / CUJ.iso / unix / 1992.txt < prev    next >
Encoding:
Text File  |  1996-02-07  |  3.2 MB  |  100,450 lines

Text Truncated. Only the first 1MB is shown below. Download the file for the complete contents.
  1.  
  2.  
  3.  
  4.  
  5.  
  6.  
  7.  
  8.  
  9.  
  10.  
  11.  
  12.  
  13.  
  14.  
  15.  
  16.  
  17.  
  18.  
  19.  
  20.  
  21.  
  22.  
  23.  
  24.  
  25.  
  26.  
  27.  
  28.  
  29.  
  30.  
  31.  
  32.  
  33.  
  34.  
  35.  
  36.  
  37.  
  38.  
  39.  
  40.  
  41.  
  42.  
  43.  
  44.  
  45.  
  46.  
  47.  
  48.  
  49.  
  50.  
  51.  
  52.  
  53.  
  54.  
  55.  
  56.  
  57.  
  58.  
  59.  
  60.  
  61.  
  62.  
  63.  
  64.  
  65.  
  66.  
  67.  
  68.  
  69.  
  70.  
  71.  
  72.  
  73.  
  74.  
  75.  
  76.  
  77.  
  78.  
  79.  
  80.  
  81.  
  82.  
  83.  
  84.  
  85.  
  86.  
  87.  
  88.  
  89.  
  90.  
  91.  
  92.  
  93.  
  94.  
  95.  
  96.  
  97.  
  98.  
  99.  
  100.  
  101.  
  102.  
  103.  
  104.  
  105.  
  106.  
  107.  
  108.  
  109.  
  110.  
  111.  
  112.  
  113.  
  114.  
  115.  
  116.  
  117.  
  118.  
  119.  
  120.  
  121.  
  122.  
  123.  
  124.  
  125.  
  126.  
  127.  
  128.  
  129.  
  130.  
  131.  
  132.  
  133.  
  134.  
  135.  
  136.  
  137.  
  138.  
  139.  
  140.  
  141.  
  142.  
  143.  
  144.  
  145.  
  146.  
  147.  
  148.  
  149.  
  150.  
  151.  
  152.  
  153.  
  154.  
  155.  
  156.  
  157.  
  158.  
  159.  
  160.  
  161.  
  162.  
  163.  
  164.  
  165.  
  166.  
  167.  
  168.  
  169.  
  170.  
  171.  
  172.  
  173.  
  174.  
  175.  
  176.  
  177.  
  178.  
  179.  
  180.  
  181.  
  182.  
  183.  
  184.  
  185.  
  186.  
  187.  
  188.  
  189.  
  190.  
  191.  
  192.  
  193.  
  194.  
  195.  
  196.  
  197.  
  198.  
  199.  
  200.  
  201.  
  202.  
  203.  
  204.  
  205.  
  206.  
  207.  
  208.  
  209.  
  210.  
  211.  
  212.  
  213.  
  214.  
  215.  
  216.  
  217.  
  218.  
  219.  
  220.  
  221.  
  222.  
  223.  
  224.  
  225.  
  226.  
  227.  
  228.  
  229.  
  230.  
  231.  
  232.  
  233.  
  234.  
  235.  
  236.  
  237.  
  238.  
  239.  
  240.  
  241.  
  242.  
  243.  
  244.  
  245.  
  246.  
  247.  
  248.  
  249.  
  250.  
  251.  
  252.  
  253.  
  254.  
  255.  
  256.  
  257.  
  258.  
  259.  
  260.  
  261.  
  262.  
  263.  
  264.  
  265.  
  266.  
  267.  
  268.  
  269.  
  270.  
  271.  
  272.  
  273.  
  274.  
  275.  
  276.  
  277.  
  278.  
  279.  
  280.  
  281.  
  282.  
  283.  
  284.  
  285.  
  286.  
  287.  
  288.  
  289.  
  290.  
  291.  
  292.  
  293.  
  294.  
  295.  
  296.  
  297.  
  298.  
  299.  
  300.  
  301.  
  302.  
  303.  
  304.  
  305.  
  306.  
  307.  
  308.  
  309.  
  310.  
  311.  
  312.  
  313.  
  314.  
  315.  
  316.  
  317.  
  318.  
  319.  
  320.  
  321.  
  322.  
  323.  
  324.  
  325.  
  326.  
  327.  
  328.  
  329.  
  330.  
  331.  
  332.  
  333.  
  334.  
  335.  
  336.  
  337.  
  338.  
  339.  
  340.  
  341.  
  342.  
  343.  
  344.  
  345.  
  346.  
  347.  
  348.  
  349.  
  350.  
  351.  
  352.  
  353.  
  354.  
  355.  
  356.  
  357.  
  358.  
  359.  
  360.  
  361.  
  362.  
  363.  
  364.  
  365.  
  366.  
  367.  
  368.  
  369.  
  370.  
  371.  
  372.  
  373.  
  374.  
  375.  
  376.  
  377.  
  378.  
  379.  
  380.  
  381.  
  382.  
  383.  
  384.  
  385.  
  386.  
  387.  
  388.  
  389.  
  390.  
  391.  
  392.  
  393.  
  394.  
  395.  
  396.  
  397.  
  398.  
  399.  
  400.  
  401.  
  402.  
  403.  
  404.  
  405.  
  406.  
  407.  
  408.  
  409.  
  410.  
  411.  
  412.  
  413.  
  414.  
  415.  
  416.  
  417.  
  418.  
  419.  
  420.  
  421.  
  422.  
  423.  
  424.  
  425.  
  426.  
  427.  
  428.  
  429.  
  430.  
  431.  
  432.  
  433.  
  434.  
  435.  
  436.  
  437.  
  438.  
  439.  
  440.  
  441.  
  442.  
  443.  
  444.  
  445.  
  446.  
  447.  
  448.  
  449.  
  450.  
  451.  
  452.  
  453.  
  454.  
  455.  
  456.  
  457.  
  458.  
  459.  
  460.  
  461.  
  462.  
  463.  
  464.  
  465.  
  466.  
  467.  
  468.  
  469.  
  470.  
  471.  
  472.  
  473.  
  474.  
  475.  
  476.  
  477.  
  478.  
  479.  
  480.  
  481.  
  482.  
  483.  
  484.  
  485.  
  486.  
  487.  
  488.  
  489.  
  490.  
  491.  
  492.  
  493.  
  494.  
  495.  
  496.  
  497.  
  498.  
  499.  
  500.  
  501.  
  502.  
  503.  
  504.  
  505.  
  506.  
  507.  
  508.  
  509.  
  510.  
  511.  
  512.  
  513.  
  514.  
  515.  
  516.  
  517.  
  518.  
  519.  
  520.  
  521.  
  522.  
  523.  
  524.  
  525.  
  526.  
  527.  
  528.  
  529.  
  530.  
  531.  
  532.  
  533.  
  534.  
  535.  
  536.  
  537.  
  538.  
  539.  
  540.  
  541.  
  542.  
  543.  
  544.  
  545.  
  546.  
  547.  
  548.  
  549.  
  550.  
  551.  
  552.  
  553.  
  554.  
  555.  
  556.  
  557.  
  558.  
  559.  
  560.  
  561.  
  562.  
  563.  
  564.  
  565.  
  566.  
  567.  
  568.  
  569.  
  570.  
  571.  
  572.  
  573.  
  574.  
  575.  
  576.  
  577.  
  578.  
  579.  
  580.  
  581.  
  582.  
  583.  
  584.  
  585.  
  586.  
  587.  
  588.  
  589.  
  590.  
  591.  
  592.  
  593.  
  594.  
  595.  
  596.  
  597.  
  598.  
  599.  
  600.  
  601.  
  602.  
  603.  
  604.  
  605.  
  606.  
  607.  
  608.  
  609.  
  610.  
  611.  
  612.  
  613. Porting C Libraries To C ++
  614.  
  615.  
  616. David Brumbaugh
  617.  
  618.  
  619. David Brumbaugh is a project manager at Advanced Information Services, a
  620. systems integrator in Peoria, IL. He has been programming in C for over five
  621. years and in C++ for over a year. He can be reached by mail at 2807 N. Renwood
  622. Ave., Peoria, IL 61604.
  623.  
  624.  
  625. Most C programmers have large investments in existing C libraries. If C++ is
  626. to fulfill the promise of reusable software, the C libraries of today cannot
  627. suddenly become obsolete. Usually, existing libraries can be given "OOPness"
  628. by encapsulating their functions in C++ classes. Furthermore, if those classes
  629. are well-designed, they can enhance an application's portability, readability,
  630. and maintainability.
  631.  
  632.  
  633. C++ And C
  634.  
  635.  
  636. For all practical purposes, C++ is a superset of C. C++ can be linked with
  637. code compiled by a C compiler if the functions are prototyped and enclosed in
  638. the linkage specifier:
  639. extern "C" { }
  640. This declaration lets you use C libraries directly, without modification. Of
  641. course, you must have a compatible object file to begin with. You can link a
  642. Turbo C 2.0 library and a Turbo/Borland C+ + program, but you cannot use the
  643. declaration to link a Turbo/Borland C+ + program and a Microsoft C 5.1
  644. library. (I know because I tried.) While this scheme doesn't exploit C+ +'s
  645. bent towards OOP, almost anything your old C libraries can do can be made to
  646. work in C++.
  647.  
  648.  
  649. Terms
  650.  
  651.  
  652. Cutting away the object-oriented jargon leaves little difference between a
  653. method and a function. Objects contain both data and functions. In OO jargon,
  654. these are attributes and methods. (For the purposes of this article, I use
  655. "method" to denote a class's member function and "function" to mean a
  656. traditional C function.) In many cases libraries already contain most of the
  657. code for the methods. To make the libraries OO, you simply need to wrap
  658. classes around the libraries.
  659.  
  660.  
  661. Preparation
  662.  
  663.  
  664. In many cases, it is not necessary to wrap libraries. A library that is not
  665. often used or has no common thread probably is not a candidate for wrapping.
  666. On the other hand, a method may need some functions found in a library that is
  667. not wrapped itself. In this case, simply enclose the C function in extern "C"
  668. { } and call it as necessary. Often you can use conditional compilation in the
  669. library's header file. (See Listing 1. The __cplusplus macro is Turbo/Borland
  670. C++ specific.)
  671. Before wrapping a class around a library, ask yourself what advantages you
  672. expect. Typically, these might include:
  673. Common Access. By overloading methods, it isn't necessary to create functions
  674. named replaceString, replaceInt, replaceReal. A replace method will suffice.
  675. Code becomes more readable and, in turn, more maintainable.more readable and,
  676. in turn, more maintainable.
  677. Reusable Code. Object-oriented code is more reusable because it encourages
  678. encapsulation. In traditional programming, programmers tend to reuse code by
  679. using an editor's "cut and paste" functions. While this works, the same code
  680. in different locations frequently requires slight changes in variable names,
  681. function names, or control structures. An object-oriented style enhances code
  682. reusability by localizing and formalizing the areas being changed.
  683. Localized Impact of Changes. Changes in understanding, requirements, and
  684. design prompt changes in program code. A typical problem arises when two areas
  685. of a program call the same function. If that function is subsequently modified
  686. to accommodate a change in one of the two areas, the other area may behave
  687. improperly. An object-oriented solution creates a descendant of the object in
  688. the first area, where the change is needed. All other code remains the same.
  689. Again, the change is local and formal.
  690. Portability of Applications. By isolating platform-specific libraries from
  691. your application in private and protected methods, you can change the
  692. implementation details of a specific class without touching the actual
  693. application code (this is a variation on number three). While preparing to
  694. wrap the library in a class, remember that in C++, all functions need
  695. prototypes. Most libraries come with a header file containing the prototypes.
  696. If your library doesn't come with such a header file, you must make one in
  697. order to use the functions in C++.
  698. C++ has several keywords not found in C: asm, catch, class, delete, friend,
  699. inline, new, operator, private, protected, public, template, this and
  700. virtual[1]. If any of your library functions have these names, you will have
  701. problems. To work around this, remove the prototype of the offending function
  702. from the header file using conditional compilation. Write a new function, with
  703. a different name, in C that calls the library function (see Listing 2 for an
  704. example).
  705. The final consideration for encapsulation concerns the availability of source
  706. code. If you have purchased the source code for a commercial library, you may
  707. want to avoid the overhead of wrapping, and simply use the cut and paste
  708. method to build classes from the original source.
  709.  
  710.  
  711. Designing Classes Around Libraries
  712.  
  713.  
  714. Since a class encapsulates both data and functions, you must identify data
  715. structures in the library that can become attributes of your class. As a
  716. general rule, data structures should be made protected variables. You will
  717. want to group common functions and data structures into a class.
  718. Once you have identified the common data structures, determine the class
  719. protocol. A class protocol is the list of methods used to send messages to
  720. objects of the class. In C++, the class protocol is the set of public methods
  721. and data items. Use generic names for the methods in your protocol. A database
  722. library may have several read functions: db_read_int(), db_read_str(),
  723. db_read_float(), etc. The protocol for this set of functions would be read(int
  724. &), read(char *), read(float &), etc. The bodies of these methods would
  725. contain calls to the proper library functions.
  726. Avoid the temptation to give class methods library-specific names. Create
  727. general classes that permit inheritance in the future. For example, a generic
  728. database class could have descendants that call two completely different
  729. database libraries. Changing from one database to another would require no
  730. changes to the application.
  731. Just because a function is in a library does not mean it must be represented
  732. in the class. In the example that follows, the Pfm_List class concerns itself
  733. with a single table in a database. It does not need database creation or
  734. administration functions.
  735. Round out the class with internal methods. In C++, methods designated as
  736. private or protected are internal and are used by other methods. The library
  737. will generally dictate the internal methods, but don't allow it to dictate the
  738. overall class concept.
  739.  
  740.  
  741. Pinnacle File Manager Tables
  742.  
  743.  
  744. I have translated into C+ + an example that I wrote for an earlier CUJ
  745. article, "Object-Oriented Programming In C" (July 1990). I used the Pinnacle
  746. File Manager v3.5 by Vermont Database Corporation as the library to
  747. encapsulate. (If you wish to run the code in the examples, you can obtain a
  748. free sample disk that is limited to 100 records per table from Vermont
  749. Database Corporation by calling 802-253-4437.)
  750. I first needed to decide how to implement the list concept I presented in July
  751. 1990. Turbo C++ came with a Container class library, which met some of the
  752. needs I was trying to meet with the LIST_CLASS. Should I try to fit my code
  753. into their hierarchy or create my own hierarchy? I finally decided to use my
  754. own. This hierarchy is shown in Figure 1.
  755. A D_List defines the operations you would normally perform on a list. The list
  756. will be ordered in some way, even if only in a physical order. The list will
  757. have a "top" and an "end," and will include the concept of a "current" member.
  758. The methods in Table 1 are common to all lists.
  759.  
  760.  
  761.  
  762. The Files
  763.  
  764.  
  765. LISTCLAS.H (Listing 3) defines the abstract class D_List. LISTCLAS.CPP
  766. (Listing 4) defines the source code. Note the large number of purely virtual
  767. functions. The D_Array descendant of D_List is a list in memory. Since it uses
  768. no commercial libraries, it is outside the scope of this article. The next
  769. class in the hierarchy, Pfm_List, is a class specifically designed to use the
  770. protocol defined in D_List to access a Pinnacle File Manager database table.
  771. This is a case of making a library fit your design, rather than designing
  772. around a library.
  773. Because the scope of the class is limited to a single list (table), wrapping
  774. the Pinnacle File Manager functions does not take full advantage of the
  775. manager's advanced features. Since the class hierarchy was in place before I
  776. had PFM, I will add more features to the class when I need them, either
  777. directly or through inheritance. I may also choose to create a friend class to
  778. apply some of the other features.
  779. PINCLASS.H (Listing 5) defines Pfm_List. Note the extern "C" { } around the
  780. inclusion of PINNACLE.H, informing the C++ compiler that I intend to use the C
  781. functions defined in pinnacle.h. The data structures used by Pinnacle, DB and
  782. DBTAB, are private, providing enforced data encapsulation. The other methods
  783. (functions) are defined as public. In addition to the required constructors
  784. and destructors, two additional methods, DB_Handle() and TableHandle(),
  785. interface with other classes and functions requiring database access.
  786. PINCLASS.CPP (Listing 6) contains the source code for the class Pfm_List. Note
  787. that the DB_ functions are the same functions defined between extern "C"
  788. brackets. This is where we "wrap" the C+ + classes around the library.
  789. The sample application is an imaginary payroll list. PAYLIST.H and PAYLIST.CPP
  790. (Listing 7 and Listing 8) define the class and code for the class specific to
  791. the application.
  792. PTEST.CPP (Listing 9) tests the classes. It is simply a small program that
  793. runs through the major methods of the classes. Note that with the exception of
  794. the constructor, there is no database specific code. You can see that if the
  795. company decided to port the PC application elsewhere, all the changes would
  796. occur within the PayList class.
  797. Other files include the script for creating the database (Listing 10) and the
  798. database itself (PAYROLL.DB), which is included on the code disk.
  799.  
  800.  
  801. Conclusion
  802.  
  803.  
  804. One of the major advantages of C++ is its compatibility with the vast number
  805. of existing C libraries. In addition to protecting your investment, the
  806. encapsulation of libraries can enhance maintainability, portability, and ease
  807. of use.
  808.  
  809.  
  810. Bibliography
  811.  
  812.  
  813. [1] Stevens, A1, Teach Yourself C++, MIS:Press, Portland Oregon, 1990.
  814. Figure 1
  815. Table 1
  816. Method Purpose
  817. ----------------------------------------------------------------------------
  818. at_top Return TRUE if current member is top member.
  819. at_end Return TRUE if current member is last member.
  820. is_empty Return TRUE if LIST is empty, FALSE otherwise.
  821. find Search the list for an implementation defined member. If not
  822.  found don't change currency.
  823. prev Make the member previous to this one current. If current
  824.  member is top, do nothing.
  825. next Make the member after this one current. If current member is
  826.  last, do nothing.
  827. seek Search to a position in the list. Use like fseek.
  828. top Make the top member current.
  829. end Make the last member current.
  830. display Display the current member.
  831. add_member Add a new member to the list.
  832. replace_member Replace data in current member.
  833. current Return a pointer to the current member.
  834. total_members Return the total number of members in the list.
  835. tell Return the position, from the start of the list, of the
  836.  current member. The top member is 0.
  837.  
  838. Listing 1 Example of Header File with and C++ Prototypes
  839. example1.h:
  840.  
  841. #ifdef __cplusplus
  842. extern "C" {
  843. #endif
  844.  
  845. int foo(int x);
  846. int bar(int x);
  847.  
  848. #ifdef __cplusplus
  849. }
  850. #endif
  851.  
  852.  
  853.  
  854. Listing 2 Example of C Functions with C++ Key Word Names
  855. This code doesn't work:
  856.  
  857. new.cpp:
  858. extern int new();
  859.  
  860. main()
  861. {
  862. int x = new();
  863. }
  864.  
  865. So replace it with this code:
  866.  
  867. example2.h:
  868. #ifndef __cplusplus
  869. extern int new();
  870. #endif
  871. #ifdef __cplusplus
  872. extern int new_one();
  873. #endif
  874.  
  875. example2.c:
  876.  
  877. #include "example2.h"
  878.  
  879. int new_one()
  880. {
  881. return(new());
  882. }
  883.  
  884.  
  885. new2.cpp:
  886.  
  887. #include "example2.h"
  888.  
  889. main()
  890. {
  891. int x = new_one();
  892. }
  893.  
  894. /* End of File */
  895.  
  896.  
  897. Listing 3 (listclas.h)
  898. //////////////////////////////////////////////////////
  899. // D_List Class - Similar to list class
  900. // developed for CUJ July, 1990
  901. //
  902. // Dave's List.
  903. //
  904. //////////////////////////////////////////////////////
  905. #ifndef LISTCLAS_H
  906. #define LISTCLAS_H
  907. #include <stdio.h>
  908. enum Boolean {false, true};
  909.  
  910. class D_List {
  911.  
  912. public:
  913. virtual Boolean at_top()
  914. { return ((Boolean) (tell() == 0L));}
  915. virtual Boolean at_end0 : 0;
  916. virtual Boolean is_empty()
  917. { return ((Boolean) (total() == 0L)); }
  918. virtual Boolean find(void *key) = 0;
  919. virtual void prev() = 0,
  920. next() = 0,
  921. seek(long where, int start),
  922. top() = 0,
  923. end() = 0,
  924. add() = 0,
  925. replace(void *member) = 0,
  926. remove() = 0;
  927. virtual void *current() = 0;
  928. long virtual total(),
  929. tell() = 0;
  930. void * operator[] (long where)
  931. { seek(where,SEEK_SET); return current(); }
  932. void * operator[] (void *key)
  933. { return (find(key) ? current() : NULL); }
  934. };
  935. #endif
  936.  
  937. /* End of File */
  938.  
  939.  
  940. Listing 4 Source Code for C++D_List Class
  941. #include "listclas.h"
  942.  
  943.  
  944. void D_List::seek(long where, int start)
  945. {
  946. long count;
  947.  
  948. switch(start)
  949. {
  950.  
  951. case SEEK_SET:
  952. top();
  953. for (count = 0; count < where; ++count)
  954. {
  955. if (at_end())
  956. break;
  957. next();
  958. }
  959. break;
  960. case SEEK_CUR:
  961. if (where > 0)
  962. {
  963. for (count = 0; count < where; ++count)
  964. {
  965. if (at_end())
  966. break;
  967. next();
  968. }
  969. }
  970. else
  971.  
  972. {
  973. for(count = 0; count > where; ++count)
  974. {
  975. if (at_top())
  976. break;
  977. prev ();
  978. }
  979. }
  980. break;
  981. case SEEK_END:
  982. end();
  983. for(count = 0; count > where; ++count)
  984. {
  985. if (at_top())
  986. break;
  987. prev ();
  988. }
  989. break;
  990. }
  991. }
  992.  
  993. long D_List::total ()
  994. {
  995. long thisone, count;
  996.  
  997. thisone = tell();
  998. top();
  999. count = 0;
  1000. do
  1001. {
  1002. if ( ! at_end() )
  1003. {
  1004. ++count;
  1005. next();
  1006. }
  1007. } while( ! at_end() );
  1008. seek(thisone,SEEK_SET);
  1009. return(count);
  1010. }
  1011.  
  1012. // End of File
  1013.  
  1014.  
  1015. Listing 5 (pinclass.h) Pinnacle File Manager Class
  1016. #ifndef PINCLASS_H
  1017. #define PINCLASS_H
  1018.  
  1019. extern "C" {
  1020. #include <pinnacle.h>
  1021. }
  1022. #include "string.h"
  1023. #include "listclas.h"
  1024.  
  1025. class Pfm_List: public D_List {
  1026. protected:
  1027. DB db;
  1028. DBTAB table;
  1029. DBCOL default_key;
  1030. DBSEARCH default_dbsearch;
  1031.  
  1032. Boolean is_at_top, // Flags
  1033. is_at_bottom,
  1034. needs_closed;
  1035. char *buffer;
  1036. size_t max_buffer_size;
  1037. public:
  1038. // Constructors and Destructors
  1039. Pfm_List(char *database, char *table_name,
  1040. size_t mbs = 1024);
  1041. Pfm_List(DB &open_db, char *table_name,
  1042. size_t mbs = 1024);
  1043. Pfm_List(DB &open_db, DBTAB &db_table,
  1044. size_t mbs = 1024);
  1045. virtual ~Pfm_List();
  1046.  
  1047. // Database Specific Methods
  1048. DB DBHandle() {return db;}
  1049. DBTAB TableHandle() {return table;}
  1050.  
  1051. // List Status
  1052. virtual Boolean at_top()
  1053. { return( is_at_top); }
  1054. virtual Boolean at_end()
  1055. { return( is_at_bottom); }
  1056. long virtual tell();
  1057.  
  1058. // List Navigation
  1059. virtual Boolean find(void *key),
  1060. find(void *key, char *relation),
  1061. find(char *col, char *relation, void *key);
  1062. virtual void prev(), next(), top(), end();
  1063. virtual Boolean findnext(), findprev();
  1064.  
  1065. // Interface to and from List
  1066. virtual void add();
  1067. virtual void replace(char *field,
  1068. char *value);
  1069. virtual void replace(char *field,
  1070. long value);
  1071. virtual void replace(char *field,
  1072. double value);
  1073. // virtual void replace(char *field, void *value);
  1074. virtual void replace(void *member){ };
  1075. // Not truly defined.
  1076. virtual void remove();
  1077. virtual void *current()
  1078. {return (void *)buffer; }
  1079. long virtual total()
  1080. { return ((long) DB_CountRows(table));}
  1081. virtual char *get(char *field, char *value);
  1082. virtual long get(char *field, long &value);
  1083. virtual double get(char *field, double &value);
  1084. virtual void *get(char *field, void *value);
  1085.  
  1086. };
  1087. #endif
  1088.  
  1089. /* End of File */
  1090.  
  1091.  
  1092.  
  1093. Listing 6 (pinclass.cpp) Source code for Wrapping C++ around Pinnacle
  1094. ////////////////////////////////////////////////////
  1095. // David Brumbaugh, 1991
  1096. ////////////////////////////////////////////////////
  1097. #include <iostream.h>
  1098. #include <conio.h>
  1099. #include "PINCLAS.H"
  1100.  
  1101. Pfm List::Pfm_List(char *database, char *table_name,
  1102. size_t mbs)
  1103. {
  1104. db = DB_Open(database, "rw", 0);
  1105. if (db == NULL)
  1106. {
  1107. cerr << "Error opening database:"
  1108. << database << "Error ="
  1109. << DB_ErrorString() << "\n";
  1110. cerr << "Strike a Key"; getch();
  1111. return;
  1112. }
  1113. table = DB_Table(db,table_name);
  1114. if (DB_Errno != DB_OK)
  1115. {
  1116. cerr << "Error opening table:"
  1117. << table name << "Error = "
  1118. << DB_ErrorString() << "\n";
  1119. cerr << "Strike a Key";
  1120. getch();
  1121. DB_Close(db);
  1122. return;
  1123. }
  1124. DB_FirstRow(table);
  1125. DB_NextRow(table,DBNEXT);
  1126. is_at_top = true;
  1127. is_at_bottom = false;
  1128. needs_closed = true;
  1129. default_key = NULL;
  1130. default_dbsearch = NULL;
  1131. max_buffer_size = mbs;
  1132. buffer = new char[max_buffer_size];
  1133.  
  1134. }
  1135.  
  1136. Pfm_List::Pfm_List(DB &open_db, char *table_name, size_t mbs)
  1137. {
  1138. db = open_db;
  1139.  
  1140. table = DB_Table(db,table_name);
  1141. if (DB_Errno != DB_OK)
  1142. {
  1143. cerr << "Error opening table:"
  1144. << table_name << "Error = "
  1145. << DB_ErrorString() << "\n";
  1146. cerr << "Strike a Key"; getch();
  1147. return;
  1148. }
  1149. DB_FirstRow(table);
  1150. DB_NextRow(table,DBNEXT);
  1151.  
  1152. is_at_top = true;
  1153. is_at_bottom = false;
  1154. needs_closed = false;
  1155. default_key = NULL;
  1156. default_dbsearch = NULL;
  1157. max_buffer_size = mbs;
  1158. buffer = new char[max_buffer_size];
  1159.  
  1160. }
  1161.  
  1162. Pfm_List::Pfm_List(DB &open db, DBTAB &db_table,
  1163. size_t mbs)
  1164. {
  1165. db = open_db;
  1166. table = db_table;
  1167. DB_FirstRow(table);
  1168. DB_NextRow(table,DBNEXT);
  1169. is_at_top = true;
  1170. is_at_bottom = false;
  1171. needs_closed = false;
  1172. default_key = NULL;
  1173. default_dbsearch = NULL;
  1174. max_buffer_size = mbs;
  1175. buffer = new char[max_buffer_size];
  1176.  
  1177.  
  1178. }
  1179.  
  1180. Pfm_List::~Pfm_List()
  1181. {
  1182. if (needs_closed)
  1183. DB_Close(db);
  1184. delete buffer;
  1185. }
  1186.  
  1187. Boolean Pfm_List::find(void *key, char *relation)
  1188. {
  1189. if (default_key == NULL)
  1190. return(false);
  1191. default_dbsearch = DB_SearchObject(db,
  1192. DB_GetType(default_key),key,relation);
  1193. DB_FirstRow(table);
  1194. return((Boolean) DB_FindNext(default_key,
  1195. default_dbsearch,DBNEXT));
  1196.  
  1197. }
  1198.  
  1199. Boolean Pfm_List::find(char *col, char *relation,
  1200. void *key)
  1201. {
  1202. default key = DB_Column(table,col);
  1203. return(find(key,relation));
  1204. }
  1205.  
  1206. Boolean Pfm_List::find(void *key)
  1207. {
  1208. return(find(key,"=="));
  1209. }
  1210.  
  1211.  
  1212. void Pfm_List::prev()
  1213. {
  1214. if (DB_NextRow(table,DBPREVIOUS) != DB_OK)
  1215. {
  1216. is_at_top = false;
  1217. }
  1218. else
  1219. {
  1220. is_at_top = true;
  1221. if (total () > 1L)
  1222. {
  1223. is_at_bottom = false;
  1224. }
  1225. }
  1226. }
  1227.  
  1228. void Pfm_List::next()
  1229. {
  1230. if (DB_NextRow(table,DBNEXT) != DB_OK)
  1231. {
  1232. is_at_bottom = false;
  1233. }
  1234. else
  1235. {
  1236. is at bottom = true;
  1237. if (total() = 1L)
  1238. {
  1239. is_at_top = false;
  1240. }
  1241. }
  1242. }
  1243.  
  1244. void Pfm_List::top()
  1245. {
  1246. DB_FirstRow(table);
  1247. DB_NextRow(table,DBNEXT);
  1248. is_at_top = true;
  1249. if (tota1() > 1L)
  1250. is_at_bottom = false;
  1251.  
  1252. }
  1253.  
  1254. void Pfm_List::end()
  1255. {
  1256. DB_ForAllRows(table);
  1257. DB_NextRow(table,DBPREVIOUS);
  1258. is_at_bottom = true;
  1259. if (total() > 1L)
  1260. is_at_top = false;
  1261. }
  1262.  
  1263. void Pfm_List::add()
  1264. {
  1265. DB_AddRow(table);
  1266. }
  1267.  
  1268.  
  1269. void Pfm_List::replace(char *field,
  1270. char *value)
  1271.  
  1272. {
  1273. DBCOL col = DB_Column(table,field);
  1274. DB_PutString(col,(unsigned char *) value);
  1275. }
  1276.  
  1277. void Pfm_List::replace(char *field,
  1278. long value)
  1279. {
  1280. DBCOL col = DB_Column(table,field);
  1281. DB_PutInteger(col, value);
  1282. }
  1283.  
  1284. void Pfm_List::replace(char *field, double value)
  1285. {
  1286. DBCOL col = DB_Column(table,field);
  1287. DB_PutReal (col, value);
  1288. }
  1289.  
  1290. void Pfm_List::remove()
  1291. {
  1292. DB_DeleteRow(table);
  1293. next();
  1294. }
  1295.  
  1296. long Pfm_List::tell()
  1297. {
  1298. DBROWID thisrow, checkrow;
  1299. long position = 0L;
  1300.  
  1301. thisrow = DB_CurrentRow(table);
  1302. top();
  1303. do
  1304. {
  1305. checkrow = DB_CurrentRow(table);
  1306. if (checkrow != thisrow)
  1307. {
  1308. ++position;
  1309. DB_NextRow(table,DBNEXT);
  1310. }
  1311. } while(checkrow != thisrow);
  1312.  
  1313. return(position);
  1314.  
  1315. }
  1316. Boolean Pfm_List::findnext()
  1317. {
  1318. if (default_key == NULL default_dbsearch == NULL)
  1319. return(false);
  1320.  
  1321. return((Boolean) DB_FindNext(default_key,
  1322. default_dbsearch,DBNEXT));
  1323. }
  1324.  
  1325. Boolean Pfm_List::findprev()
  1326. {
  1327. if (default_key -= NULL default_dbseerch == NULL)
  1328. return(false);
  1329.  
  1330. return((Boolean) DB_FindNext(default_key,
  1331.  
  1332. default_dbsearch,DBPREVIOUS));
  1333. }
  1334.  
  1335. char *Pfm_List::get(char *field, char *value)
  1336. {
  1337. DBCOL col = DB_Column(table, field);
  1338. if (value != NULL)
  1339. {
  1340. strcpy(value,(char *)DB_GetString(col));
  1341. {
  1342. return(value);
  1343. }
  1344.  
  1345. long Pfm_List::get(char *field, long &value)
  1346. {
  1347. DBCOL col = DB_Column(table, field);
  1348. value = DB_GetInteger(col);
  1349. return(value);
  1350. }
  1351. double Pfm_List::get(char *field, double &value)
  1352. {
  1353. DBCOL col = DB_Column(table, field);
  1354. value = DB_GetReal(col);
  1355. return(value);
  1356. }
  1357.  
  1358. void *Pfm_List::get(char *field, void *value)
  1359. {
  1360.  
  1361. DBCOL col = DB_Column(table,field);
  1362. unsigned long type - DB_GetType(col);
  1363.  
  1364. if (type == Integer)
  1365. {
  1366. DBINTEGER *dbint = (DBINTEGER *) value;
  1367. get(field,*dbint);
  1368. }
  1369. else if (type == Real)
  1370. {
  1371. DBREAL *dbreal = (DBREAL*) value;
  1372. get(field,*dbreal);
  1373. }
  1374. else if (type == String)
  1375. {
  1376. get(field,(DBSTRING) value);
  1377. }
  1378. else if (type == NBytes)
  1379. {
  1380. memcpy(value,DB_GetNBytes(col,NULL),
  1381. DB_GetSize(col));
  1382. }
  1383. return(value);
  1384. }
  1385.  
  1386. // End of File
  1387.  
  1388.  
  1389. Listing 7 (paylist. h) Concrete List Class for a Payroll List
  1390. //////////////////////////////////////////////////
  1391.  
  1392. // By David Brumbaugh
  1393. //////////////////////////////////////////////////
  1394.  
  1395. #ifndef PAYLIST_H
  1396. #define PAYLIST_H
  1397. #include "pinclas.h"
  1398.  
  1399. struct employee
  1400. {
  1401. char last[21], first[11];
  1402. double pay_rate; // Dollars per day
  1403. long days_worked;
  1404. // Days worked in this pay period.
  1405. };
  1406.  
  1407. class PayList: public Pfm_List {
  1408. protected:
  1409. employee empBuffer;
  1410.  
  1411. public:
  1412. // Constructors
  1413. PayList():Pfm_List("payroll.db","Employees")
  1414. {default_key = DB_Column(table,"LastFirst"); }
  1415. PayList(DB &open_db):PfmList(open_db, "Employees")
  1416. {default_key = DB_Column(table,"LastFirst");}
  1417. PayList(DB &open_db, DBTAB &db_table):
  1418. Pfm_List(open_db, db_table)
  1419. {default_key = DB_Column(table,"LastFirst");}
  1420.  
  1421. // List Navigation
  1422. virtual Boolean find (char *last),
  1423. find(char *last, char *first);
  1424. virtual Boolean find(void *key)
  1425. {return (find( (char *) key));}
  1426.  
  1427. // List Interface
  1428. virtual void add(employee &emp);
  1429. virtual void replace(employee &emp);
  1430. virtual void get(employee &emp);
  1431. virtual void *current()
  1432. { get(empBuffer); return (void *) &empBuffer;}
  1433.  
  1434. };
  1435. #endif
  1436.  
  1437.  
  1438. Listing 8 Source Code for the "Payroll List" Class
  1439. #include "paylist.h"
  1440.  
  1441. Boolean PayList::find(char *last)
  1442. {
  1443. if (Pfm_List::find("Last","==",(void *) last))
  1444. {
  1445. get(empBuffer);
  1446. return(true);
  1447. }
  1448. return(false);
  1449. }
  1450.  
  1451.  
  1452. Boolean PayList::find(char *last, char *first)
  1453. {
  1454. if (DB_Find(table,"Last == %s && First == %s",
  1455. last, first))
  1456. {
  1457. get(empBuffer);
  1458. return(true);
  1459. }
  1460. return(false);
  1461. }
  1462.  
  1463. void PayList::add(employee &emp)
  1464. {
  1465. Pfm_List::add();
  1466. replace(emp);
  1467. }
  1468.  
  1469. void PayList::replace(employee &emp)
  1470. {
  1471. Pfm_List::replace("First",emp.first);
  1472. Pfm_List::replace("Last",emp.last);
  1473. Pfm_List::replace("Pay",emp.pay_rate);
  1474. Pfm_List::replace("Days",emp.days_worked);
  1475. empBuffer = emp;
  1476.  
  1477. }
  1478.  
  1479. void PayList::get(employee &emp)
  1480. {
  1481. Pfm_List::get("First",emp.first);
  1482. Pfm_List::get("Last",emp.last);
  1483. Pfm_List::get("Pay",emp.pay_rate);
  1484. Pfm_List::get("Days",emp.days_worked);
  1485. }
  1486.  
  1487. /* End of File */
  1488.  
  1489.  
  1490. Listing 9 Test for the Pinnacle Database List Class
  1491. /////////////////////////////////////////////////
  1492. // Copyright 1991, David Brumbaugh
  1493. /////////////////////////////////////////////////
  1494. #include "paylist.h"
  1495. #include <iostream.h>
  1496.  
  1497. main()
  1498. {
  1499.  
  1500. // Test the more abstract Class
  1501.  
  1502. Pfm_List db("payroll.db","Employees");
  1503.  
  1504. // Create Some Records
  1505. db.add();
  1506. db.replace("First","John");
  1507. db.replace("Last", "Jones");
  1508. db.replace("Pay", 11.25);
  1509. db.replace("Days",245L);
  1510.  
  1511.  
  1512. db.add();
  1513. db.replace("First","Ben");
  1514. db.replace("Last", "Franklin");
  1515. db.replace("Pay", 111.25);
  1516. db.replace("Days",3L);
  1517.  
  1518. db.add();
  1519. db.replace("First","George");
  1520. db.replace("Last", "Washington");
  1521. db.replace("Pay", 1111.25);
  1522. db.replace("Days",4L);
  1523.  
  1524. // Then Retreive Them
  1525. char first[10], last[20];
  1526. long days;
  1527. double amount;
  1528.  
  1529. db.top();
  1530. do
  1531. {
  1532. cout << db.get("First",first) <<" "<<
  1533. db.get("Last",last) <<" "<<
  1534. db.get("Pay",amount) <<" ";
  1535. db.get("Days",&days);
  1536. cout << days << '\n';
  1537. db.next();
  1538. } while(! db.at_end());
  1539. cout << '\n';
  1540. amount = 100.00;
  1541. if (db.find("Pay",">",&amount))
  1542. {
  1543. do
  1544. {
  1545. cout << db.get("First",first) <<" "
  1546. << db.get("Last",last) <<" "
  1547. << db.get("Pay",amount) <<" ";
  1548. db.get("Days",&days);
  1549. cout << days << '\n';
  1550.  
  1551. } while(db.findnext());
  1552. }
  1553. amount = 11.25;
  1554. cout << '\n';
  1555. if (db.find(&amount))
  1556. {
  1557. cout << db.get("First",first) <<" "
  1558. << db.get("Last",last) <<""
  1559. << db.get("Pay",amount) <<" ";
  1560. db.get("Days",&days);
  1561. cout << days << '\n';
  1562.  
  1563. }
  1564.  
  1565. // Now Test the application specific class
  1566. // Create Some More Records
  1567. employee emp[] = {{"Kirk","James",17.01,5},
  1568. {"Solo","Han",12.34,4},
  1569. {"Hammer","Mike",36.24,36},
  1570. {"Hill","Dixon",19.46,30}},
  1571.  
  1572. *empPtr, empBuffer;
  1573.  
  1574. PayList pl(db. DBHandle());
  1575. for(int x=0; x < 3; ++x)
  1576. {
  1577. pl .add (emp [x] );
  1578. }
  1579.  
  1580. // Then Retrieve Them Again
  1581. pl.top();
  1582. do
  1583. {
  1584. pl.get(empBuffer);
  1585. cout << empBuffer.first <<" "<<
  1586. empBuffer.last <<" "
  1587. << empBuffer.pay_rate <<" ";
  1588. cout << empBuffer.days_worked << '\n';
  1589. pl .next();
  1590. } while(! pl.at_end());
  1591.  
  1592. cout << "\n Done \n";
  1593. }
  1594.  
  1595. // End of File
  1596.  
  1597.  
  1598. Listing 10 Code to Create Database Table in PFM
  1599. PAYROLL.INC:
  1600. remove payroll.db
  1601. create payroll.db
  1602. addtab * Employees "List of Employees"
  1603. addcol * * Last "Last Name" "%s" NULL "String NoNulls"
  1604. addcol * * First "1st Name" "%s" NULL "String NoNulls"
  1605. addkey * * LastFirst +Last+First Unique
  1606. addcol * * Pay "Pay Rate" "%6.2f" NULL "Real NoNulls"
  1607. addcol * * Days "Days Worked" "%2d" NULL Integer
  1608. exit
  1609.  
  1610. /* End of File */
  1611.  
  1612.  
  1613.  
  1614.  
  1615.  
  1616.  
  1617.  
  1618.  
  1619.  
  1620.  
  1621.  
  1622.  
  1623.  
  1624.  
  1625.  
  1626.  
  1627.  
  1628.  
  1629.  
  1630.  
  1631.  
  1632.  
  1633.  
  1634.  
  1635. A Portable VMS-Style Input Line Routine
  1636.  
  1637.  
  1638. Robert Bybee
  1639.  
  1640.  
  1641. Robert Bybee is Senior Electrical Engineer at Scientific Games, Atlanta
  1642. Georgia, where he is involved with hardware and software design of
  1643. point-of-sale terminals used in state lottery systems. He was previously with
  1644. Chromatics, a manufacturer of color graphic computer systems, and Telecorp
  1645. Systems, which builds voice response and telephone call processing computers.
  1646. He has a BSEE from the University of Virginia and is currently seeking an MBA
  1647. at Georgia State University. Robert has been programming computers for 23
  1648. years, with 10 years of C experience, and has been designing hardware for over
  1649. 15 years. He can be contacted at 5011 Brougham Court, Stone Mountain GA 30087.
  1650.  
  1651.  
  1652. When a program needs to read a line of text input, the programmer often
  1653. resorts to using a standard C library function, such as gets(). Depending on
  1654. the operating system, this function will permit only minimal input editing,
  1655. perhaps limited to the backspace key.
  1656. The DEC VAX-VMS operating system, however, provides far more sophisticated
  1657. facilities to edit command-line input. The MS-DOS world has produced similar
  1658. facilities, such as the public-domain DOSEDIT, Peter Norton's NDE, and the
  1659. DOS-KEY program in MS-DOS 5.0. Of course, if you're writing code for embedded
  1660. systems or non-DOS machines, you may not be able to use any of these.
  1661. The get_str() routine presented here duplicates the functions found in these
  1662. systems. It provides a way to gather commands or input strings from the user,
  1663. emulating most of the VMS keystrokes. You can change the code easily to
  1664. respond to other keystrokes if you don't like VMS-style editing.
  1665.  
  1666.  
  1667. Command Line Recall
  1668.  
  1669.  
  1670. get_str()'s most useful feature is its ability to scroll through previously
  1671. entered commands, edit them, and transmit them again. This functionality comes
  1672. in handy when you make a typing error and must retype almost the same command.
  1673. Moreover, when running test programs, I find myself retyping the same series
  1674. of commands, time and time again. With the touch of a few cursor keys,
  1675. get_str() redisplays these earlier commands for you to enter. The number of
  1676. lines in its "history buffer" is a compile-time decision.
  1677. When entering a command, or after recalling a previous command, you often need
  1678. more than a simple backspace key to edit the command. get_str() supports left
  1679. and right cursor motion, moving to the start and end of the line, and both
  1680. insert (push-right) and overtype modes of text entry.
  1681.  
  1682.  
  1683. Display Dependencies
  1684.  
  1685.  
  1686. I wrote get_str() to support a "least common denominator" output device. The
  1687. routine will run on a PC display screen, a VT100 terminal connected to a
  1688. serial port, or nearly any other type of terminal or display, provided the
  1689. display can backspace (move the cursor left) without erasing the character
  1690. that the cursor lands on. get_str() makes no other assumptions about the
  1691. control-codes accepted by its output device.
  1692.  
  1693.  
  1694. Keyboard Dependencies
  1695.  
  1696.  
  1697. The next problem is the type of keyboard device to support. It is possible to
  1698. avoid all keyboard dependencies by forcing the operator to type
  1699. CTRL-characters for all editing operations. However, people accustomed to
  1700. using the cursor keys will find such a solution unacceptable.
  1701. Most terminals have at least a backspace key and four cursor arrow keys. VMS
  1702. lets the left and right arrow keys move the cursor in the current line. It
  1703. uses the up and down arrows to move forward and backward through previous
  1704. commands. And it uses the DELETE key on a VT100 to delete one character to the
  1705. left of the cursor. Under VMS, all other command-line editing functions are
  1706. performed using control keys. The key assignments are:
  1707. CTRL-A switches between insert
  1708.  and overtype mode
  1709. CTRL-H moves to the beginning
  1710.  of the line
  1711. CTRL-E moves to the end of the line
  1712. CTRL-R recalls the last line entered
  1713. CTRL-X erases the line
  1714. get_str() duplicates all of these, except CTRL-H. The backspace key on a VT100
  1715. terminal and PC keyboard both generate this character. There are probably
  1716. historical reasons why VMS uses this key to move the cursor to the start of
  1717. the line, and uses DELETE to erase one character.
  1718. All too often, I find myself hitting the BACKSPACE key instead of DELETE when
  1719. I want to delete a character under VMS. I can't fix VMS, but I can fix
  1720. get_str(). In this code, CTRL-B moves the cursor to the beginning of the line,
  1721. and both DELETE and BACKSPACE erase one character. If you want true VMS
  1722. emulation in this regard, change the value CTRL ('B') to BACKSPACE_KEY (line
  1723. 100), and remove the reference to BACKSPACE_KEY on line 79.
  1724.  
  1725.  
  1726. Code Description
  1727.  
  1728.  
  1729. I wrote the code in this article to compile under either Turbo C 2.0 or
  1730. Borland C++ 2.0. If you port the code to a different compiler or environment,
  1731. you need to change only the sys_getchar() and sys_putchar() functions. To
  1732. support a different keyboard, the get_char_esc() routine would also require
  1733. attention.
  1734. Listing 1 contains the get_str() function and supporting routines. Lines 22-25
  1735. define four constants, UP_ARROW, DOWN_ARROW, RIGHT_ARROW, and LEFT_ARROW,
  1736. which pass the notion of a cursor movement between get_chr_esc() and
  1737. get_str(). I defined these values to be the same as the PC keyboard scan codes
  1738. for the four arrow keys, but they could really be any four non-ASCII constants
  1739. if you don't need to read input from a PC keyboard.
  1740. Lines 30-33 declare the size of the history list, called lastlines, which
  1741. stores previously entered commands. The first and last entries in this list
  1742. must be blank, so declaring MAX_RECALL as 22 allows for the two blank strings
  1743. plus 20 prior commands. The RECSUB macro generates a subscript into the list
  1744. and handles modulo arithmetic so that the subscript is always between 0 and
  1745. MAX_RECALL minus one.
  1746. RECALL_LEN defines the longest line you can type into the get_str() routine.
  1747. You can make it as long as you like. You can also expand MAX_RECALL to permit
  1748. more than 20 commands in your buffer. All it costs is memory.
  1749. The VMS command interpreter does not treat its history list as a circular
  1750. buffer. You can't scroll around in one direction indefinitely. You can use the
  1751. up-arrow until you reach the oldest command in the list, then you get a blank
  1752. line and the up-arrow stops working. The down-arrow takes you one step toward
  1753. the most recently entered command, and it too stops working at the end of the
  1754. list.
  1755. The get_str() routine begins on line 60. You pass it the address of the buffer
  1756. in which to store the input line, and the length of that buffer. On line 73,
  1757. get_str() goes into a "forever" loop, where it gathers up characters and
  1758. performs the editing functions. It breaks out of the loop when RETURN is
  1759. pressed.
  1760. After calling the routine get_chr_esc(), which gets a character from the user,
  1761. get-str() enters a large if statement that handles all of the cursor keys and
  1762. control-characters. If the character isn't anything special, the function
  1763. stores it in the buffer.
  1764. The other functions in Listing 1 are fairly simple. cursor_right() moves the
  1765. cursor one position to the right by sending out the character that is already
  1766. under the cursor. This operation moves the cursor without relying on any
  1767. terminal-dependent control or escape codes.
  1768. cursor_left ()moves the cursor one position to the left. It depends upon the
  1769. terminal responding to CTRL-H as a backspace. In a string in C, the character
  1770. '\b' is a backspace, CTRL-H, hex value 08. Note that this value does not erase
  1771. the character to the left of the cursor. To do that, as in the function
  1772. clear_line(), a three-character string is sent out: backspace, space,
  1773. backspace.
  1774. get_str() calls the final function in Listing 1, get_char_esc(), to get a
  1775. character. This function in turn calls sys_getchar() to get a character from
  1776. the user, and passes most characters through unchanged. If, however,
  1777. get_char_esc() detects an escape (ESC), it looks at the next two characters to
  1778. see if they represent a VT100 terminal's arrow keys. A VT100 puts out the
  1779. following escape codes when you press a cursor key:
  1780. up ESC [ A
  1781.  
  1782. down ESC [ B
  1783. right ESC [ C
  1784. left ESC [ D
  1785. get_char_esc() translates these three-character sequences into a single
  1786. integer, so get_str() can process it easily. For simplicity, these integers
  1787. are the same four integer values the BIOS produces when a PC's arrow keys are
  1788. struck. If you plan to use get_str() with a terminal whose arrow keys don't
  1789. produce the VT100 escape sequences above, you must modify get_char_exc().
  1790.  
  1791.  
  1792. sys getchar And sys_putchar
  1793.  
  1794.  
  1795. get_str() uses these two functions for all of its input and output. I isolated
  1796. them in this fashion for portability, since these functions were voted "most
  1797. likely to change" when the boss says, "By the way, we're going to OS/2
  1798. tomorrow."
  1799. On lines 37-47 of Listing 2, sys_putchar() sends one character to the display.
  1800. Its only frill is that it expands a line-feed character, '\n', to a
  1801. carriage-return and line-feed pair. Depending on how you use get_str(), you
  1802. may want to prevent this translation.
  1803. sys_getchar(), found on lines 50-63, waits for a character from the user. In
  1804. the MS-DOS environment, the function calls the Turbo C bioskey() function to
  1805. wait for the next keystroke. bioskey() returns a 16-bit integer, whose high
  1806. byte is the PC keyboard scan code, and the low byte is the ASCII value of that
  1807. character (if any), or 00 if it's a non-ASCII key. sys_getchar() checks that
  1808. the key is ASCII, and if so, strips off the scan code before returning it.
  1809. If you need to do background processing while waiting for the next character,
  1810. you could modify sys_getchar() to call an idle function until a key is ready.
  1811. Listing 2 also contains a simple main() routine for testing the code. It reads
  1812. an input line using get_str() and prints the results in quotes so you can see
  1813. what was received. To leave the program, type quit.
  1814.  
  1815.  
  1816. Storing More Commands
  1817.  
  1818.  
  1819. VMS stores the most recently entered command into the bottom of the history
  1820. buffer, so that a single up-arrow keystroke will recall it. If this command
  1821. matches the previously entered command, get_str() doesn't store it, since
  1822. doing so would only waste space in the buffer.
  1823. As the program stands, if you enter two or more commands repeatedly,
  1824. EDIT XX
  1825. RUN XX
  1826. these two commands will eventually occupy the entire history list. You might
  1827. consider improving the code here: before entering a command into the list,
  1828. remove the command if it already exists anywhere in the list. While this would
  1829. allow the list to hold more unique commands, it would be the non-VMS thing to
  1830. do.
  1831.  
  1832.  
  1833. Limiting The Search
  1834.  
  1835.  
  1836. Under VMS and in get_str(), striking an up-arrow or down-arrow immediately
  1837. replaces the input line with the previous (or next) entry from the history
  1838. list. If you accidentally hit an up-arrow while typing a command, everything
  1839. you've typed is lost.
  1840. Some MS-DOS keyboard enhancers have a different philosophy. If you type one or
  1841. two characters and then press the up-arrow, you will scroll through a subset
  1842. of the history list. Your subset is limited to those entries beginning with
  1843. the characters you have already typed. If you wanted to recall a command that
  1844. began with the letter D, you would simply type "D" and a few up-arrows until
  1845. the command appeared.
  1846. This feature resembles the "command completion" functionality built into some
  1847. flavors of UNIX. On those systems, you can type a partial filename or command,
  1848. press ESC, and the system will fill in the remainder of the name if it can.
  1849. You could modify get_str() to include this functionality. The code that
  1850. handles the up and down arrow keys, lines 120-142 of Listing 1, would skip any
  1851. entries that didn't match the partial input line. You would also keep a copy
  1852. of that partial line, otherwise the next up-arrow keystroke would overwrite
  1853. it.
  1854. Something as simple as an input-line reader can really enhance the usefulness
  1855. of your programs. At the time I wrote this code, I was working for a die-hard
  1856. VAX person, who was very pleased to see a VMS-like front-end attached to our
  1857. embedded-system diagnostics. If your users are familiar with VMS, the
  1858. get_str() routine will warm their hearts, too.
  1859.  
  1860. Listing 1
  1861. /*
  1862. * Get input string, with VMS-style input line editing
  1863. * and previous-command scrolling.
  1864. *
  1865. * Written for Turbo C 2.0 / Borland C++ 2.0
  1866. * Bob Bybee, 2/91
  1867. */
  1868. #include <stdio.h>
  1869. #include <string.h>
  1870.  
  1871. /* ASCII key definitions */
  1872. #define ESC_KEY 0x1b
  1873. #define DELETE_KEY 0x7f
  1874. #define BACKSPACE_KEY 0x08
  1875. #define RETURN_KEY 0x0d
  1876. #define CTRL(x) ((x) & 0x1f)
  1877.  
  1878. /* Arbitrary values for tracking cursor key entry.
  1879. * These happen to match PC BIOS scan codes, but any
  1880. * unique values would work.
  1881. */
  1882. #define UP_ARROW 0x4800
  1883. #define DOWN_ARROW 0x5000
  1884.  
  1885. #define RIGHT_ARROW 0x4d00
  1886. #define LEFT_ARROW 0x4b00
  1887.  
  1888. /* MAX_RECALL is two greater than the number of lines
  1889. * we want in the "lastlines" recall buffer.
  1890. */
  1891. #define MAX_RECALL 22
  1892. #define RECSUB(x) (((x) + MAX_RECALL) % MAX_RECALL)
  1893. #define RECALL_LEN 100
  1894.  
  1895. static char lastlines[MAX_RECALL][RECALL_LEN];
  1896. static int num_got; /* # chars in input buffer */
  1897. static int cursor_pos; /* cursor position on line */
  1898. static int last_ptr = 0; /* ptr to last line entered */
  1899. static char erase_one[] = "\b \b"; /* erase one character */
  1900. static char *str_ptr; /* ptr to current input string */
  1901.  
  1902.  
  1903. /* prototypes for this file */
  1904. static void clear_line( void );
  1905. static int cursor_right( void );
  1906. static int cursor_left( void );
  1907. static int get_char_esc( void );
  1908. static void put_str( char *str );
  1909.  
  1910. /* external functions (see listing2.c) */
  1911. void sys_putchar( char ch );
  1912. int sys_getchar( void );
  1913.  
  1914.  
  1915.  
  1916. /*
  1917. * get_str() is called by main() to get a line of input.
  1918. * The input line is placed in "str" and will be no
  1919. * more than "len" characters.
  1920. */
  1921. void get_str( char *str, int len )
  1922. {
  1923. int i, c, curptr, insert_mode = 1;
  1924.  
  1925. num_got = 0;
  1926. cursor_pos = 0;
  1927. str_ptr = str; /* copy the buffer pointer */
  1928. curptr = RECSUB(lastptr + 1);
  1929. lastlines[curptr][0] = '\0';
  1930. lastlines[RECSUB(curptr + 1)][0] = '\0';
  1931. if (len > RECALL_LEN - 1) /* limit len to RECALL_LEN */
  1932. len = RECALL_LEN - 1;
  1933.  
  1934. while (1)
  1935. {
  1936. c = get_char_esc();
  1937.  
  1938. if (c == RETURN_KEY)
  1939. break;
  1940. else if (c == DELETE_KEY c == BACKSPACE_KEY)
  1941. {
  1942. if (cursor_left())
  1943. {
  1944.  
  1945. ++cursor_pos;
  1946. for (i = cursor_pos; i < num_got; ++i)
  1947. {
  1948. str[i - 1] = str[i];
  1949. sys_putchar(str[i]);
  1950. }
  1951. sys_putchar(' ');
  1952. for(i = cursor_pos; i <= num_got; ++i)
  1953. sys_putchar('\b');
  1954. -num_got;
  1955. -cursor_pos;
  1956. }
  1957. }
  1958. else if (c == CTRL('X')) /* erase line? */
  1959. clear_line();
  1960. else if (c == CTRL('A')) /* insert/overtype? */
  1961. insert_mode ^= 1;
  1962. else if (c == CTRL('B')) /* beginning-of-line? */
  1963. {
  1964. while (cursor_left())
  1965. ;
  1966. }
  1967. else if (c == CTRL('E')) /* end-of-line? */
  1968. {
  1969. while (cursor_right())
  1970. ;
  1971. }
  1972. else if (c == CTRL('R')) /* recall last line? */
  1973. {
  1974. clear_line();
  1975. strcpy(str, lastlines[lastptr]);
  1976. if ((num_got = strlen(str)) > 0)
  1977. {
  1978. put_str(str);
  1979. break;
  1980. }
  1981. }
  1982. else if (c == UP_ARROW)
  1983. {
  1984. clear_line();
  1985. if (lastlines[curptr][0] != '\0' 
  1986. lastlines[RECSUB(curptr - 1)][0] !: '\0')
  1987. {
  1988. curptr = RECSUB(curptr - 1);
  1989. strcpy(str, lastlines[curptr]);
  1990. put_str(str);
  1991. cursor_pos = num_got = strlen(str);
  1992. }
  1993. }
  1994. else if (c == DOWN_ARROW)
  1995. {
  1996. clear_line();
  1997. if (lastlines[curptr][0] != '\0' 
  1998. lastlines[RECSUB(curptr + 1)][0] != '\0')
  1999. {
  2000. curptr = RECSUB(curptr + 1);
  2001. strcpy(str, lastlines[curptr]);
  2002. put_str(str);
  2003. cursor_pos = num_got = strlen(str);
  2004.  
  2005. }
  2006. }
  2007. else if (c == LEFT_ARROW)
  2008. {
  2009. if (cursor_pos > 0)
  2010. {
  2011. sys_putchar('\b');
  2012. -cursor_pos;
  2013. }
  2014. }
  2015. else if (c == RIGHT_ARROW)
  2016. cursor_right();
  2017. else if (' ' <= c && c < 0x7f && num_got < len - 1)
  2018. {
  2019. if (insert_mode)
  2020. {
  2021. /* Move right, all the characters
  2022. * to the right of cursor_pos.
  2023. */
  2024. for (i = num_got; i > cursor_pos; -i)
  2025. str[i] = str[i - 1];
  2026. str[cursor_pos] = c;
  2027. for (i = cursor_pos; i <= num_got; ++i)
  2028. sys_putchar(str[i]);
  2029. for (i = cursor_pos; i < num_got; ++i)
  2030. sys_putchar('\b');
  2031. ++num_got;
  2032. ++cursor_pos;
  2033. }
  2034. else /* insert is off, use overtype mode */
  2035. {
  2036. str[cursor_pos] = c;
  2037. sys_putchar(c);
  2038. if (cursor_pos == num_got)
  2039. ++num_got;
  2040. ++cursor_pos;
  2041. }
  2042. }
  2043. }
  2044.  
  2045. str[num_got] = '\0';
  2046. sys_putchar('\n');
  2047.  
  2048. /* If this line is non-empty, and different
  2049. * from the last one accepted, store it into
  2050. * the recall buffer.
  2051. */
  2052. if (num_got > 0 && strcmp(str, lastlines[lastptr]) ! = 0)
  2053. {
  2054. lastptr = RECSUB(lastptr + 1);
  2055. strcpy (lastlines[lastptr], str);
  2056. }
  2057. }
  2058.  
  2059.  
  2060. /*
  2061. * Move the cursor right one position, by echoing the
  2062. * character it's currently over.
  2063. * Return 1-OK, 0-can't move.
  2064.  
  2065. */
  2066. static int cursor_right( void )
  2067. {
  2068. if (cursor_pos < num_got)
  2069. {
  2070. sys_putchar(str_ptr[cursor_pos]);
  2071. ++cursor_pos;
  2072. return (1);
  2073. }
  2074. return (0);
  2075. }
  2076.  
  2077. /*
  2078. * Move the cursor left one position, by echoing
  2079. * a backspace. Return l-OK, 0-can't move.
  2080. */
  2081. static int cursor_left( void )
  2082. {
  2083. if (cursor_pos > 0)
  2084. {
  2085. sys_putchar('\b');
  2086. -cursor_pos;
  2087. return (1);
  2088. }
  2089. return (0);
  2090. }
  2091.  
  2092.  
  2093. /*
  2094. * Erase all characters on the current line.
  2095. */
  2096. static void clear_line( void )
  2097. {
  2098. while (cursor_right())
  2099. ; /* move right, to end of line */
  2100. cursor_pos = 0;
  2101. while (num_got > 0)
  2102. {
  2103. put_str(erase_one); /* then, erase to left */
  2104. -num_got;
  2105. }
  2106. }
  2107.  
  2108.  
  2109. /*
  2110. * Get a character, with escape processing.
  2111. * Handles special sequences like "ESC [ A" for up-arrow.
  2112. * This function would need to be modified to handle
  2113. * keyboards that are neither PC's nor VT-100's.
  2114. */
  2115. static int get_char_esc( void )
  2116. {
  2117. int ch;
  2118.  
  2119. ch = sys_getchar();
  2120. if (ch != ESC_KEY)
  2121. return (ch);
  2122.  
  2123. ch = sys_getchar();
  2124.  
  2125. if (ch != '[')
  2126. return (ch);
  2127.  
  2128. ch = sys_getchar();
  2129. if (ch == 'A')
  2130. return (UP_ARROW); /* was ESC [ A */
  2131. else if (ch == 'B')
  2132. return (DOWN_ARROW); /* was ESC [ B */
  2133. else if (ch == 'C')
  2134. return (RIGHT_ARROW); /* was ESC [ C */
  2135. else if (ch == 'D')
  2136. return (LEFT_ARROW); /* was ESC [ D */
  2137. else
  2138. return (ch);
  2139. }
  2140.  
  2141.  
  2142. /*
  2143. * Put a string to sys_putchar().
  2144. */
  2145. static void put_str( char *str )
  2146. {
  2147. while (*str != '\0')
  2148. sys_putchar(*str++);
  2149. }
  2150. /* End of File */
  2151.  
  2152.  
  2153. Listing 2
  2154. /*
  2155. * Listing 2: main() routine and test code for get_str().
  2156. * Includes the OS-dependent routines sys_getchar()
  2157. * and sys putchar().
  2158. */
  2159. #include <stdio.h>
  2160. #include <stdlib.h>
  2161. #include <bios.h>
  2162. #include <string.h>
  2163.  
  2164. #define INBUFSIZ 70
  2165. char inbuf[INBUFSIZ + 1];
  2166.  
  2167. void get_str( char *str, int len );
  2168.  
  2169. void main( void )
  2170. {
  2171.  
  2172. while (1)
  2173. {
  2174. printf("\ntype 'quit' to quit.\nprompt> ");
  2175. get_str(inbuf, INBUFSIZ);
  2176.  
  2177. if (stricmp(inbuf, "quit") == 0)
  2178. break;
  2179.  
  2180. printf(" Got: \"%s\"\n", inbuf);
  2181. }
  2182. }
  2183.  
  2184.  
  2185.  
  2186. /*********************************************************
  2187. * The following two routines will need to be changed,
  2188. * in order to use get_str() in a different environment.
  2189. ********************************************************/
  2190.  
  2191. /*
  2192. * Put a character to the output device.
  2193. * Expand \n to \r\n.
  2194. */
  2195. void sys_putchar( char ch )
  2196. {
  2197.  
  2198. putchar(ch);
  2199. if (ch == "\n")
  2200. putchar("\r");
  2201. }
  2202.  
  2203.  
  2204. /*
  2205. * Get a character from the input device.
  2206. * Use the BIOS call so we can detect arrow keys.
  2207. */
  2208. int sys_getchar( void )
  2209. {
  2210. int ch;
  2211.  
  2212. ch=bioskey(0); /* wait and get a key */
  2213.  
  2214. if ((ch & 0xff) != 0) /* if not an extended key, */
  2215. ch &= 0xff; /* use only the
  2216. ASCII part */
  2217. return (ch);
  2218. }
  2219. /* End of File */
  2220.  
  2221.  
  2222.  
  2223.  
  2224.  
  2225.  
  2226.  
  2227.  
  2228.  
  2229.  
  2230.  
  2231.  
  2232.  
  2233.  
  2234.  
  2235.  
  2236.  
  2237.  
  2238.  
  2239.  
  2240.  
  2241.  
  2242.  
  2243.  
  2244.  
  2245.  
  2246.  
  2247.  
  2248. An OS/2 MIDI Device Driver
  2249.  
  2250.  
  2251. Carl M. Benda
  2252.  
  2253.  
  2254. Mr. Benda is a programmer in the Charlotte Lab of the Services Sector Division
  2255. of IBM. He holds an MS in computer science from the University of North
  2256. Carolina - Charlotte, and an MS in materials engineering from Worcester
  2257. Polytechnic Institute.
  2258.  
  2259.  
  2260. The market currently offers many excellent software packages that include
  2261. device drivers for the MPU-401, the standard PC Musical Instrument Digital
  2262. Interface (MIDI). Almost all of the packages, however, run under MS-DOS, while
  2263. almost none runs under OS/2. No commercial driver interface at the time of
  2264. this writing is available for OS/2 2.0. (The 32-bit version of OS/2 currently
  2265. in beta-test.) This article partially fills the gap by describing a simple
  2266. device driver written mostly in C for handling interrupt-driven I/O to and
  2267. from the Roland MPU-401.
  2268.  
  2269.  
  2270. Writing Device Drivers For OS/2
  2271.  
  2272.  
  2273. There are several reasons why so few commercial MIDI device drivers exist for
  2274. OS/2. First, OS/2 drivers are more complex than their MS-DOS counterparts. To
  2275. understand OS/2 device drivers, it is necessary to look at the structure of
  2276. the operating system and see where the device driver fits into the operating
  2277. system (Figure 1).
  2278. In MS-DOS, any program can read and write to any portion of the RAM, whereas
  2279. in OS/2, device drivers are required to handle hardware addresses and
  2280. interrupt handling. OS/2 device drivers exist in a multitasking environment
  2281. and so must handle requests from multiple processes and threads of execution
  2282. within a single process. Before the driver can complete a request from one
  2283. process, it may have to handle additional requests from a different process.
  2284. OS/2 device drivers also have the special responsibility of being well behaved
  2285. in terms of not disrupting other portions of the system. Since drivers run at
  2286. the RING 0 execution level, they have ultimate access to all of the system's
  2287. memory and resources. If an OS/2 device driver does not have a proper
  2288. initialization routine, the system will crash at boot time.
  2289. OS/2 device drivers in most implementations consist of two segments. The first
  2290. segment is for data, and the second is for code. This scheme presents two
  2291. problems when writing the code for the device driver entirely in C.
  2292. First, the C compiler for OS/2 puts the code segment first in the program and
  2293. the data segment last. An OS/2 device driver, however, must have the data
  2294. segment first in the program. In this data segment, the first item must be the
  2295. device header. The device header contains startup information that is critical
  2296. to how the operating system treats that device driver. Second, it is necessary
  2297. to prevent the C compiler from inserting the C startup code before the device
  2298. header. I accomplish this by linking in a small assembly language function.
  2299.  
  2300.  
  2301. Implementing The Device Driver
  2302.  
  2303.  
  2304. This article presents all the necessary pieces for generating a MIDI device
  2305. driver. This OS/2 device driver handles interrupts from the Roland MPU-IMC,
  2306. which is a PS/2 Micro Channel adapter card that generates interrupts using IRQ
  2307. 9. When the card generates an interrupt, the device driver gathers the
  2308. information from the adapter's addresses: 0x331h for the commands and status,
  2309. and 0x330h for any data bytes coming from other MIDI devices. From these
  2310. addresses the device driver places the data into a shared memory buffer that
  2311. may be accessed by an application program.
  2312. The assembly code in Listing 1 serves as the startup code to replace the C
  2313. compiler's normal startup code. The remaining portions for a minimal MIDI OS/2
  2314. device driver appear in Listing 2, Listing 3, Listing 4, and Listing 5.
  2315. The OS/2 device driver must be linked with a definitions file which has the
  2316. name of the MIDI library as the entry point into the device driver. For this
  2317. device driver, a definitions file would contain the lines
  2318. LIBRARY MIDI
  2319. PROTMODE
  2320. The OS/2 config.sys file must reference the subdirectory and filename of the
  2321. device driver so that at boot time, the OS/2 kernel can load the device driver
  2322. and run the Init routine.
  2323. OS/2 is much like DOS in the way it handles interrupts. When the hardware
  2324. generates a level 9 interrupt, the operating system immediately runs the code
  2325. entry point named by the SetIRQ routine. Thus OS/2 responds to the hardware in
  2326. more of a real-time fashion.
  2327.  
  2328.  
  2329. Conclusion
  2330.  
  2331.  
  2332. The next logical step from this device driver would be to write an application
  2333. that uses its interface.
  2334. MIDI Data Management
  2335. MIDI data is transmitted by sending and receiving single byte pieces of
  2336. information. MIDI commands are composed of one, two, or three bytes of data
  2337. arranged and transmitted one after another. The first byte is called the
  2338. status byte, the following one or two bytes represent which note to be played
  2339. and at what velocity, respectively. A protocol at the heart of the MIDI
  2340. structure facilitates writing software designed to handle MIDI data. In the
  2341. protocol, the command byte's most significant bit is used to determine if that
  2342. particular byte is a command byte (bit set to 1), or a data byte (bit set to
  2343. 0). By interpreting the first bit, a MIDI device could continuously play a
  2344. note (note on status), until a new command byte was encountered.
  2345. Figure 1
  2346.  -----------------------------------
  2347.  Your Application 
  2348.  R ------------------------------
  2349.  I File Manager
  2350.  N R ---------------------
  2351.  I OS/2 KERNEL R
  2352.  3 N --------------- I
  2353.  G Device Driver N
  2354.  --------------- G
  2355.  ____________________O
  2356.  2
  2357.  -----------------------------
  2358.  
  2359.  ----------------------------------
  2360.  
  2361. Listing 1 (mididrv.asm)
  2362.  
  2363. ;
  2364. ; Musical Instrument Digital Interface
  2365. ; Assembler routines for an OS/2 Device
  2366. ; Driver written in C.
  2367. ;
  2368. ; Copyright Carl M. Benda 1991
  2369.  
  2370. EXTRN _main:near
  2371. EXTRN _DevHlp:dword
  2372. EXTRN _interrupt_handler:near
  2373. PUBLIC _STRAT
  2374. PUBLIC _INT_HNDLR
  2375. PUBLIC _SetIRQ
  2376. PUBLIC _ReadBytes
  2377. PUBLIC _WriteBytes
  2378. PUBLIC _int3
  2379. PUBLIC _acrtused
  2380.  
  2381. _DATA segment word public 'DATA'
  2382. _DATA ends
  2383.  
  2384. CONST segment word public 'CONST'
  2385. CONST ends
  2386.  
  2387. _BSS segment word public 'BSS'
  2388. _BSS ends
  2389.  
  2390. DGROUP group CONST, _BSS,_DATA
  2391.  
  2392. _TEXT segment word public 'CODE'
  2393.  
  2394.  
  2395.  
  2396.  
  2397. ASSUME cs:_TEXT, ds:DGROUP, es:NOTHING, ss:NOTHING
  2398.  
  2399. .286P
  2400. ;
  2401. ; C startup routine for mididrv.c
  2402. ; _Strat is called by 0S/2 during
  2403. ; boot time.
  2404. ;
  2405.  
  2406. _STRAT proc far
  2407. __acrtused: ;prevent startup C
  2408. push 0 ;used as the dev value
  2409. push es ;send request packet address
  2410. push bx
  2411. call _main ;call driver main function
  2412. pop bx ;restore bx
  2413. pop es ;restore es
  2414. add sp,2 ;get rid of pushed 0
  2415. retf
  2416.  
  2417. _STRAT endp
  2418.  
  2419.  
  2420. _INT_HNDLR proc far
  2421. pusha ;save registers
  2422.  
  2423. push ds
  2424. push es
  2425. call _interrupt_handler ;handle interrupts
  2426. mov al,9h ;int value ;interrupt number.
  2427. mov dl,31h ;devhlp EOI ;DevHlp End of int
  2428. call [_DevHlp] ;call DevHlp entry
  2429. pop es
  2430. pop ds
  2431. popa ;restore everything
  2432. retf ;return to C function.
  2433.  
  2434. _INT_HNDLR endp
  2435.  
  2436. ;
  2437. ; These next routines are used by the C code
  2438. ; because these procedures need to call special
  2439. ; device driver helper routines requiring
  2440. ; hardware registers of specific values.
  2441. ;
  2442.  
  2443. _SetIRQ proc near
  2444. push bp
  2445. mov bp,sp
  2446. ; flag located at bp + 8
  2447. ; fnc located at bp + 6
  2448. ; irq irq number at + 4
  2449.  
  2450. mov bx,WORD PTR [bp+4] ;irq number
  2451. mov ax,WORD PTR [bp+6] ;fnc entry
  2452. mov dh,BYTE PTR [bp+8] ;share flag
  2453. mov dl,1Bh ;SetIRQ DevHlp
  2454. call [_DevHlp]
  2455. leave
  2456. ret
  2457.  
  2458. _SetIRQ ENDP
  2459.  
  2460. _ReadBytes proc near
  2461. push bp
  2462. mov bp,sp
  2463. pusha
  2464. push es
  2465. push ds
  2466.  
  2467. ; Physical Offset = 4
  2468. ; Physical Segment = 6
  2469. ; Device Driver = 8
  2470. ; count = 12
  2471.  
  2472. mov ax,WORD PTR [bp+6]
  2473. mov bx,WORD PTR [bp+4]
  2474. mov dl,15h ;PhysToVirt
  2475. mov dh,00h ;result in es:di
  2476. call [_DevHlp] ;useable addr.
  2477.  
  2478. lds si,DWORD PTR [bp+ 8] ;source
  2479. mov cx, WORD PTR [bp+12] ;bytes to move
  2480. rep movsb ;copy data!
  2481.  
  2482.  
  2483. mov dl,32h ;Must UnPhys!
  2484. call [_DevHlp]
  2485.  
  2486. pop ds
  2487. pop es
  2488. popa
  2489. leave
  2490. ret
  2491.  
  2492. _ReadBytes ENDP
  2493.  
  2494. _WriteBytes proc near
  2495. push bp
  2496. mov bp,sp
  2497. pusha
  2498. push es
  2499. push ds
  2500.  
  2501. ; Physical Offset = 4
  2502. ; Physical Segment = 6
  2503. ; Device Driver = 8
  2504. ; count = 12
  2505.  
  2506. mov ax,WORD PTR [bp+6]
  2507. mov bx,WORD PTR [bp+4]
  2508. mov dl,15h ;PhysToVirt
  2509. mov dh,00h ;result in ds:si
  2510. call [_DerHlp] ;useable addr.
  2511.  
  2512. les di,DWORD PTR [bp+ 8] ;source
  2513. mov cx, WORD PTR [bp+12] ;bytes to move
  2514. rep movsb ;copy data!
  2515.  
  2516. mov dl,32h ;Must UnPhys!
  2517. cal1 [_DevH1p]
  2518.  
  2519. pop ds
  2520. pop es
  2521. popa
  2522. leave
  2523. ret
  2524.  
  2525. _WriteBytes ENDP
  2526.  
  2527. _int3 proc near
  2528. int 3
  2529. ret
  2530. _int3 endp
  2531.  
  2532. _TEXT ends
  2533. end
  2534.  
  2535. ; End of File
  2536.  
  2537.  
  2538. Listing 2 (midic.c)
  2539. /****************************************************/
  2540. /* */
  2541. /* Musical Instrument Digital Interface 0S/2 */
  2542.  
  2543. /* Device Driver, primarily written in Microsoft */
  2544. /* C (tm). */
  2545. /* */
  2546. /* Copyright IBM 1991. */
  2547. /* */
  2548. /* */
  2549. /****************************************************/
  2550.  
  2551.  
  2552.  
  2553. #include "midic.h"
  2554.  
  2555. /****************************************************/
  2556. /* */
  2557. /* Device Header must be the FIRST data area */
  2558. /* */
  2559. /****************************************************/
  2560.  
  2561. DeviceHeader devhdr = {
  2562. (void far *) -1,
  2563. (DAW_CHR DAW_OPN DAW_LEVEL),
  2564. {void near *) STRAT,
  2565. {void near *) 0,
  2566. {'M','I','D','I','$',' ',' ',' '},
  2567. {0}
  2568. };
  2569.  
  2570. /****************************************************/
  2571. /* */
  2572. /* These next variables must be initialized all to */
  2573. /* zero. Also, the MsgData is used not only to */
  2574. /* display the Device Driver message during the */
  2575. /* boot process, but is also used to write the end */
  2576. /* of data offset into the Request Header. */
  2577. /* */
  2578. /****************************************************/
  2579.  
  2580. unsigned far *DevHlp=0L;/* Storage area for DevHlps */
  2581. unsigned char rx_queue[10]={0}; /* receiver queue */
  2582. unsigned char tx_queue[10]={0}; /* transmitter queue*/
  2583.  
  2584. InitExit far *InitExt=OL; /* pointer to InitExit */
  2585. DeviceRead far *DevRead=OL; /* points to read strc */
  2586. DeviceWrite far *DevWrite=OL;/* points to write str */
  2587.  
  2588. unsigned char MsgData[] = "Midi Device Driver \r\n";
  2589.  
  2590. /*********************************/
  2591. /* */
  2592. /* Device Initialization Routine */
  2593. /* is called only once during */
  2594. /* the OS/2 BOOT process. The */
  2595. /* address of the DevHlp */
  2596. /* */
  2597. /*********************************/
  2598.  
  2599. void Init(InitEntry far *InitPtr, int dev)
  2600. {
  2601.  
  2602.  
  2603. DevHlp = (unsigned far *)InitPtr->DevHlp;
  2604. /* callable DevHlp */
  2605. /* entry point.. */
  2606.  
  2607.  
  2608.  
  2609. /*********************************/
  2610. /* */
  2611. /* During Initialization we can */
  2612. /* install the interrupt handler */
  2613. /* which will allow OS/2 to know */
  2614. /* where the code is that will */
  2615. /* run, each time the interrupt */
  2616. /* 9 happens. */
  2617. /* */
  2618. /*********************************/
  2619.  
  2620. DOSPUTMESSAGE(1, 8, devhdr.name);
  2621. DOSPUTMESSAGE(1,strlen(MsgData),MsgData);
  2622.  
  2623.  
  2624. SetIRQ(9,(void near *)INT_HNDLR,0);
  2625.  
  2626.  
  2627. /* output initialization message */
  2628.  
  2629. /* send back our cs and ds end values to os/2 */
  2630.  
  2631. InitExt = (InitExit far *)InitPtr;
  2632. InitExt->code_off = (unsigned short)last_code;
  2633. InitExt->data_off = (unsigned short)MsgData+
  2634. strlen(MsgData);
  2635.  
  2636. return;
  2637. }
  2638.  
  2639. /* common entry point for calls to strat routines */
  2640.  
  2641. void main(ReqHeader far *rp, int dev )
  2642. {
  2643.  
  2644. switch(rp->req_cmd)
  2645. {
  2646. case INIT:
  2647.  
  2648. Init((InitEntry far *)rp,dev);
  2649.  
  2650. rp->req_stat = DONE;
  2651. return;
  2652.  
  2653. case OPEN:
  2654.  
  2655. /****************************/
  2656. /* */
  2657. /* During OPEN, place MPU */
  2658. /* into "DUMB" mode. */
  2659. /* */
  2660. /****************************/
  2661.  
  2662.  
  2663. outp(MIDI_CMD,DUMB_MODE);
  2664.  
  2665. rp->req_stat = DONE;
  2666. return;
  2667.  
  2668. case CLOSE:
  2669.  
  2670. /****************************/
  2671. /* */
  2672. /* During CLOSE, reset MPU */
  2673. /* mode. */
  2674. /* */
  2675. /****************************/
  2676.  
  2677. outp(MIDI_CMD,MPU RESET);
  2678. rp->req_stat = DONE;
  2679. return;
  2680.  
  2681. case READ:
  2682.  
  2683.  
  2684. rx_queue[0] = inp(MIDI_DATA);
  2685. DevRead = (DeviceRead far *)rp;
  2686.  
  2687. ReadBytes((unsigned long)DevRead->buff_addr,
  2688. (unsigned long)rx_queue,
  2689. 1);
  2690.  
  2691. rp->req_stat = DONE;
  2692. return;
  2693.  
  2694.  
  2695. case WRITE:
  2696.  
  2697. DevWrite = (DeviceWrite far *)rp;
  2698.  
  2699. WriteBytes((unsigned long)DevWrite->buff_addr,
  2700. (unsigned long)tx_queue,
  2701. 1);
  2702.  
  2703. rp->req_stat = DONE;
  2704. return;
  2705.  
  2706. case IOCTL:
  2707. rp->req_stat = DONE;
  2708. return;
  2709.  
  2710. default:
  2711. rp->req_stat = DONE;
  2712. return;
  2713.  
  2714. }
  2715. }
  2716.  
  2717. /***************************/
  2718. /* */
  2719. /* This interrupt handler */
  2720. /* is called by _INT_HNDLR */
  2721. /* from the assembler pro- */
  2722.  
  2723. /* cedure. After return- */
  2724. /* ing, the DevHlp End of */
  2725. /* Interrupt function. */
  2726. /* */
  2727. /***************************/
  2728.  
  2729. void near interrupt_handler ()
  2730. {
  2731. rx_queue[0] = inp(0x0330);
  2732. }
  2733.  
  2734. /*************************/
  2735. /* */
  2736. /* Last code offset.... */
  2737. /* */
  2738. /*************************/
  2739.  
  2740. void last_code() {}
  2741.  
  2742. /* End of File */
  2743.  
  2744.  
  2745. Listing 3 (midic.h)
  2746. /************************************/
  2747. /* */
  2748. /* MIDIC - 0S/2 device driver */
  2749. /* Header File copyright */
  2750. /* IBM 1991 */
  2751. /* */
  2752. /* */
  2753. /* */
  2754. /* This header file contains the */
  2755. /* required OS/2 device driver */
  2756. /* structures for the Drive header */
  2757. /* the Request Header, the INIT_IN */
  2758. /* packet, the INIT_OUT packet,and */
  2759. /* for this particular device */
  2760. /* driver, also the ReadWrite pack- */
  2761. /* et. */
  2762. /* */
  2763. /************************************/
  2764.  
  2765. void near last_code();
  2766.  
  2767.  
  2768. extern void near STRAT();
  2769. extern void near ReadBytes(unsigned long,
  2770. unsigned long,
  2771. unsigned short);
  2772. extern void near WriteBytes(unsigned long,
  2773. unsigned long,
  2774. unsigned short);
  2775. extern void near int3();
  2776. extern void near SetIRQ(unsigned short,
  2777. void near *,
  2778. unsigned short);
  2779. extern void near INT_HNDLR();
  2780. extern int pascal far DOSPUTMESSAGE(unsigned int,
  2781. unsigned int,
  2782.  
  2783. unsigned char far *);
  2784.  
  2785. #define MIDI_DATA 0x0330
  2786. #define MIDI_CMD 0x0331
  2787. #define DUMB_MODE 0x003f
  2788. #define MPU_RESET 0x00ff
  2789.  
  2790. #define DAW_CHR 0x8000
  2791. #define DAW_IDC 0x4000
  2792. #define DAW_IBM 0x2000
  2793. #define DAW_SHR 0x1000
  2794. #define DAW_OPN 0x0800
  2795. #define DAW_LEVEL 0x0080
  2796. #define DAW_GIO 0x0040
  2797. #define DAW_CLK 0x0008
  2798. #define DAW_NUL 0x0004
  2799. #define DAW_SCR 0x0002
  2800. #define DAW_KBD 0x0001
  2801.  
  2802. #define ERR 0x8000
  2803. #define DEV 0x4000
  2804. #define BUSY 0x0200
  2805. #define DONE 0x0100
  2806.  
  2807. #define INIT 0x00
  2808. #define MEDIA_CHECK 0x01
  2809. #define BUILD_BPB 0x02
  2810. #define READ 0x04
  2811. #define READ_NO_WAIT 0x05
  2812. #define INPUT_STATUS 0x06
  2813. #define INPUT_FLUSH 0x07
  2814. #define WRITE 0x08
  2815. #define WRITE_VERIFY 0x09
  2816. #define OUTPUT_STATUS 0x0a
  2817. #define OUTPUT_FLUSH 0x0b
  2818. #define OPEN 0x0d
  2819. #define CLOSE 0x0e
  2820. #define REMOVABLE 0x0f
  2821. #define IOCTL 0x10
  2822. #define RESET 0x11
  2823. #define GET_DRIVE_MAP 0x12
  2824. #define SET_DRIVE_MAP 0x13
  2825. #define DEINSTALL 0x14
  2826. #define PARTITIONABLE 0x16
  2827. #define GET_FIXED_MAP 0x17
  2828.  
  2829. typedef struct DeviceHdr {
  2830. struct DeviceHdr far *PtrNextHdr; /* -1, no more */
  2831. unsigned short HdrAttr; /* hdr attribute */
  2832. void near *Strat; /* offset of_strat */
  2833. void near *IDC; /* offset of IDC */
  2834. unsigned char name[8]; /* name of driver */
  2835. char reserved[8]; /* reserved. */
  2836. } DeviceHeader;
  2837.  
  2838. typedef struct RQpacket {
  2839. unsigned char req_pac_len; /* length of packet */
  2840. unsigned char req_unit; /* use for BLOCK DD */
  2841. unsigned char req_cmd; /* which command... */
  2842.  
  2843. unsigned short req_stat; /* returned status. */
  2844. unsigned long req_resrvd; /* for use by OS/2. */
  2845. unsigned long req_link; /* used for queueing*/
  2846. } ReqHeader;
  2847.  
  2848. typedef struct InitEnt {
  2849. ReqHeader InitEntHdr; /* space for header */
  2850. unsigned char units; /* number of units. */
  2851. unsigned char far *DevHlp; /* Address of DevHlp*/
  2852. unsigned char far *InitCmd; /* ptr to commands. */
  2853. unsigned short drive; /* drive # block DD */
  2854. } InitEntry;
  2855.  
  2856. typedef struct InitExt {
  2857. ReqHeader InitExtHdr; /* space for header */
  2858. unsigned char units; /* number of units. */
  2859. unsigned short code_off; /* offset of code. */
  2860. unsigned short data_off; /* offset of data. */
  2861. unsigned char far *BPBptr; /* address of BPB. */
  2862. } InitExit;
  2863.  
  2864. typedef struct Read {
  2865. ReqHeader ReadHdr; /* space for header */
  2866. unsigned char media_des; /* media descriptor */
  2867. unsigned long buff_addr; /* physical address */
  2868. unsigned short count; /* number of bytes */
  2869. unsigned long start_sel; /* starting sel # */
  2870. unsigned short reserved; /* for use by OS/2 */
  2871. } DeviceRead;
  2872.  
  2873. typedef struct Write {
  2874. ReqHeader WriteHdr; /* space for header */
  2875. unsigned char media_des; /* media descriptor */
  2876. unsigned long buff_addr; /* physical address */
  2877. unsigned short count; /* number of bytes */
  2878. unsigned long start_sel; /* starting sel # */
  2879. unsigned short reserved; /* for use by OS/2 */
  2880. } DeviceWrite;
  2881.  
  2882. /* End of File */
  2883.  
  2884.  
  2885. Listing 4 (midic.def)
  2886. LIBRARY MIDIC
  2887. PROTMODE
  2888.  
  2889.  
  2890. Listing 5 (makefile)
  2891. # makefile for sample OS/2 device driver
  2892.  
  2893. midic.sys: mididrv.obj midic.obj
  2894. link /nod /noi /map
  2895. mididrv+midic,midic.sys,midic,doscalls+slibce,midic.def;
  2896.  
  2897.  
  2898. mididrv.obj: mididrv.asm
  2899. masm -Mx -t -N -l mididrv.asm;
  2900.  
  2901. midic.obj: midic.c midic.h
  2902.  
  2903. cl -c -Asnw -Gs -G2 -Zl -Zp -Ox -Fc midic.c
  2904.  
  2905.  
  2906.  
  2907.  
  2908.  
  2909.  
  2910.  
  2911.  
  2912.  
  2913.  
  2914.  
  2915.  
  2916.  
  2917.  
  2918.  
  2919.  
  2920.  
  2921.  
  2922.  
  2923.  
  2924.  
  2925.  
  2926.  
  2927.  
  2928.  
  2929.  
  2930.  
  2931.  
  2932.  
  2933.  
  2934.  
  2935.  
  2936.  
  2937.  
  2938.  
  2939.  
  2940.  
  2941.  
  2942.  
  2943.  
  2944.  
  2945.  
  2946.  
  2947.  
  2948.  
  2949.  
  2950.  
  2951.  
  2952.  
  2953.  
  2954.  
  2955.  
  2956.  
  2957.  
  2958.  
  2959.  
  2960.  
  2961.  
  2962.  
  2963.  
  2964.  
  2965.  
  2966. Image Processing
  2967.  
  2968.  
  2969. Part 6: Advanced Edge Detection
  2970.  
  2971.  
  2972.  
  2973.  
  2974. Dwayne Phillips
  2975.  
  2976.  
  2977. Dwayne Phillips works as a computer and electronics engineer with the United
  2978. States Department of Defense. He has a Ph.D. in electrical and computer
  2979. engineering at Louisiana State University. His interests include computer
  2980. vision, artificial intelligence, software engineering; and programming
  2981. languages.
  2982.  
  2983.  
  2984.  
  2985.  
  2986. Introduction
  2987.  
  2988.  
  2989. This is the sixth in a series of articles on images and image processing. The
  2990. first five articles in the series discussed reading and writing images (TIFF
  2991. format), displaying them, printing them, histograms and histogram
  2992. equalization, and basic edge detection. This article will cover some advanced
  2993. techniques in edge detection.
  2994. There are many different methods of edge detection. Image Processing Part 5:
  2995. Writing Images to Files and Basic Edge Detection (CUJ, October 1991) discussed
  2996. some basic techniques. This article discusses some unusual and advanced ideas
  2997. and looks at four edge detectors. The first two do not use the convolution
  2998. operation -- they use only subtraction. The third edge detector can vary the
  2999. level of detail of the edges it will detect. The fourth edge detector will
  3000. detect edges in unevenly lit images. Then an edge detector is used to enhance
  3001. the appearance of an original image. Finally, these new operators are
  3002. integrated into the C Image Processing System (CIPS). Photograph 1 shows the
  3003. original image used by all the operators.
  3004. The first edge detector is the homogeneity operator[1] which uses subtraction
  3005. to find an edge. Figure 1 shows an example of this operator. The operator
  3006. subtracts each of the pixels next to the center of a 3x3 area from the center
  3007. pixel. The result is the maximum of the absolute value of these subtractions.
  3008. Subtraction in a homogeneous region (one that is a solid gray level) produces
  3009. zero and indicates an absence of edges. A region containing sharp edges, such
  3010. as area 2 of Figure 1, has a large maximum.
  3011. The first section of Listing 1 shows the homogeneity function. This function
  3012. is similar in form to the edge detectors discussed in the previous article of
  3013. this series. First, the function checks to see if the output image exists. If
  3014. the output image does not exist, the homogeneity function creates it by
  3015. calling create_allocate_tiff_file. The homogeneity function reads in the image
  3016. array and goes into the processing loop.
  3017. In the loop over ROWS and COLS, the code performs the subtraction and finds
  3018. the maximum absolute value of the subtractions. The homogeneity operator
  3019. requires thresholding (which you can specify). A perfectly homogeneous 3x3
  3020. area is rare in an image. If you do not threshold, the result looks like a
  3021. faded copy of the original. Thresholding at 30 to 50 for a 256 gray level
  3022. image gives good results.
  3023. Photograph 2 shows the result of the homogeneity operator. This operator gives
  3024. a good rendition of the edges in the original house image. This is a quick
  3025. operator that performs only subtraction -- eight operations per pixel -- and
  3026. no multiplication.
  3027. The next edge detector is the difference operator, another simple operator
  3028. that uses subtraction. Recall that edge detection is often called image
  3029. differentiation (detecting the slope of the gray levels in the image). The
  3030. difference operator performs differentiation by calculating the differences
  3031. between the pixels that surround the center of a 3x3 area.
  3032. Figure 2 shows an example of the difference operator. The difference operator
  3033. finds the absolute value of the differences between opposite pixels, the upper
  3034. left minus lower right, upper right minus lower left, left minus right, and
  3035. top minus bottom. The result is the maximum absolute value. The results shown
  3036. in Figure 2 are similar but not exactly equal to those from the homogeneity
  3037. operator in Figure 1.
  3038. The second part of Listing 1 shows the difference_edge function, which is
  3039. similar to the homogeneity function. The routine creates an output image file
  3040. if necessary and reads in the input image array. The difference_edge function
  3041. loops over the input image array and calculates the absolute values of the
  3042. four differences. You can specify that difference_edge threshold the output.
  3043. As in the homogeneity case, the difference operator requires thresholding.
  3044. Photograph 3 shows the result of the difference edge detector. This result is
  3045. similar to the shown in Photograph 2. The difference edge detector did detect
  3046. more of the brick and mortar lines than the homogeneity operator. You can
  3047. choose between the two edge detectors depending on how much detail you want.
  3048. The difference operator is faster than the homogeneity operator. The
  3049. difference operator uses only four integer subtractions per pixel, while the
  3050. homogeneity operator uses eight subtractions per pixel. These compare to the
  3051. nine multiplications and additions for the convolution-based edge detectors
  3052. discussed in the fifth article.
  3053. The next operator to examine is the difference of Gaussians edge detector,
  3054. which allows you to vary the width of a convolution mask and adjust the detail
  3055. in the output[2,3]. The results in Photograph 2 and Photograph 3 are good.
  3056. Suppose, however, we wanted to detect only the edges of the large objects in
  3057. the house image (door, windows, etc.) and not detect the small objects
  3058. (bricks, leaves, etc.).
  3059. You can use convolution masks and vary the width of the mask to eliminate
  3060. details. If a mask is wide, then convolving an image will smooth out details,
  3061. much like averaging. If you look at stock market prices by the minute, you see
  3062. many variations. If you look at the average stock market price over each hour,
  3063. the variations begin to disappear. If you look at the average stock market
  3064. price over each week, then the variations disappear and you see general trends
  3065. in prices. If you convolve an image with a wide, constant mask, you smooth the
  3066. image. If you use a narrower, varying mask, the details remain.
  3067. Figure 3 shows two example masks. These masks are difference of Gaussians or
  3068. Mexican hat functions. The center of the masks is a positive peak (16 in the
  3069. 7x7 masks -- 19 in the 9x9 mask). The masks slope downward to a small negative
  3070. peak (-3 in both masks) and back up to zero. The curve in the 9x9 mask is
  3071. wider than that in the 3x3 mask. Notice how the 9x9 mask hits its negative
  3072. peak 3 pixels away from the center while the 7x7 masks hits its peak 2 pixels
  3073. away from the center. Also, notice these masks use integer values. Most edge
  3074. detectors of this type use floating point numbers that peak at +1. Using
  3075. integers greatly increases the speed; my 80286 machine does not have a math
  3076. co-processor and numerous floating point calculations are out of the question.
  3077. Figure 4 illustrates how the narrower mask will detect small edges the wide
  3078. mask misses. Each area in Figure 4 has a small pattern similar to the brick
  3079. and mortar pattern in the house image. This pattern has small objects (bricks)
  3080. with many edges. If you convolve the 7x7 mask in Figure 3 with the 7x7 area in
  3081. Figure 4, the result is +40; the 7x7 mask detected an edge at the center of
  3082. the 7x7 area. If you convolve the 9x9 mask in Figure 3 with the 9x9 area in
  3083. Figure 4, the result is -20; the 9x9 mask did not detect any edges. The "hat"
  3084. in the 9x9 mask was wide enough to smooth out the edges and not detect them.
  3085. The first section of Listing 2 shows the two masks and the function
  3086. gaussian_edge. gaussian_edge has the same form as the other edge detectors.
  3087. The routine creates an output image file if necessary and reads the input
  3088. image array. An additional size parameter (either 7 or 9) specifies mask
  3089. width. The inner loop over a and b varies with this parameter. The processing
  3090. portion is the same as the other convolution mask edge detectors presented in
  3091. "Image Processing, Part 5." With gaussian_edge, you can use thresholding to
  3092. produce a clear edge image or you can leave it off to show the strength of the
  3093. edges.
  3094. Photograph 4 shows the result of edge detection using the narrower 7x7 mask
  3095. and Photograph 5 shows the result of the wider 9x9 mask. The narrower mask
  3096. (Photograph 4) detected all the edges of the bricks, roof shingles, and
  3097. leaves. The wider mask (Photograph 5) did not detect the edges of small
  3098. objects, only edges of the larger objects. Photograph 4 may be too cluttered
  3099. for your application, so you would use the wider mask. If you want fine
  3100. detail, then the narrower mask is the one to choose.
  3101. You can modify other edge detectors to detect edges on different size objects.
  3102. The homogeneity operator can take the difference of the center pixel and a
  3103. pixel that is two or three pixels away. The difference edge detector can take
  3104. the difference of opposite pixels in a 5x5 or 7x7 area instead of a 3x3 area.
  3105. The quick mask (see Part 5) can change from 3x3 to 5x5 with the center value
  3106. equal to 4 and the four corners equal to -1. Try these changes as a homework
  3107. project. You need the code in gaussian_edge that changes the size of the
  3108. convolution and the call to the fix_edges function.
  3109. One problem with detecting edges involves uneven lighting in images. The
  3110. contrast-based edge detector[4] helps take care of this problem. In well lit
  3111. areas of an image the edges have large differences in gray levels. If the same
  3112. edge occurs in a poorly lit area of the image, the difference in gray levels
  3113. is much smaller. Most edge detectors result in a strong edge in the well-lit
  3114. area and a weak edge in the poorly-lit area.
  3115. The contrast-based edge detector takes the result of any edge detector and
  3116. divides it by the average value of the area. This division removes the effect
  3117. of uneven lighting in the image. You find the average value of an area by
  3118. convolving the area with a mask containing all 1s and dividing by the size of
  3119. the area.
  3120. Figure 5 illustrates the contrast-based edge detector. You can use almost any
  3121. edge detector as the basis for this operation. Figure 5 uses the quick edge
  3122. detector from the last article (Part 5). The edge in the well-lit area is an
  3123. obvious and strong edge. Convolving the quick mask with this area yields 100.
  3124. The edge in the poorly-lit area is easy to see, but convolving with the quick
  3125. mask results in 10, a weak edge that thresholding would probably eliminate.
  3126. This distinction should be avoided. The well-lit and poorly-lit edges are very
  3127. similar; both change from one gray level to another gray level that is twice
  3128. as bright.
  3129. Dividing by the average gray level in the area corrects this discrepancy.
  3130. Figure 5 shows the smoothing mask that calculates the average gray level.
  3131. Convolving the well-lit area yields an average value of 83. Convolving the
  3132. poorly-lit area yields an average value of 8. Dividing (integer division) the
  3133. strong edge in the well-lit area by 83 yields 1. Dividing the weak edge by 8
  3134. also result of 1. The two edges from unevenly-lit areas yield the same answer
  3135. and you have consistent edge detection.
  3136. The last section of Listing 1 shows the contrast_edge function that follows
  3137. the same steps as the other edge detector functions. The difference is in the
  3138. processing loop over a and b, which calculates two convolutions: sum_n (the
  3139. numerator or quick edge output) and sum_d (the smoothing output). After the
  3140. loops over a and b, divide sum_d by 9 and divide sum_n by this result. The
  3141. e_mask at the beginning of Listing 1 replaces the quick mask (from Part 5),
  3142. with every element in the quick mask multiplied by 9. You need the larger
  3143. values -- dividing by the average gray level could reduce the strength of all
  3144. edges to zero.
  3145. Photograph 6 shows the result of the regular quick edge detector. Photograph 7
  3146. shows the result of dividing the quick edge result by the average value to
  3147. produce contrast-based edge detection. Notice the inside of the upstairs and
  3148. downstairs windows. Photograph 6 (quick edge) shows edges inside the
  3149. downstairs windows, but not inside the upstairs windows. Photograph 7
  3150. (contrast-based) shows details inside the downstairs and upstairs windows.
  3151. Refer to the original image (Photograph 1). The downstairs windows are shaded
  3152. and the upstairs windows are not. Contrast-based edge detection gives better
  3153. results in uneven lighting.
  3154. You can use contrast-based edge detection with any edge detector. As a
  3155. project, try modifying the homogeneity edge detector by dividing its result by
  3156. the average gray level. But first multiply the result of homogeneity by a
  3157. factor (9 or more) so dividing does not reduce edge strengths to zero. Modify
  3158. any of the other edge detectors in a similar manner.
  3159. A good application of edge detectors is to enhance edges and improve the
  3160. appearance of an original image. Detect the edges in an image and then overlay
  3161. these edges on top of the original image to accent its edges. The last section
  3162. of Listing 2 shows the enhance_edges function, which repeats the quick_edge
  3163. function from the last article with a few added lines of code. Examine the
  3164. code right after the loops over a and b. If the result of convolution (the sum
  3165. variable) is greater than a user-chosen threshold, then the output image is
  3166. assigned that value. If not, then the output image is assigned the value from
  3167. the input image. The result is the input image with its strong edges accented.
  3168. Photograph 8 shows the result of enhancing the edges of Photograph 1. The
  3169. edges of the bricks, the siding in the left, and the lines on the garage door
  3170. are all sharper.
  3171. You can use any edge detector to enhance the edges in an input image. You just
  3172. add the option of taking the edge detector result or a value from the input
  3173. image. An interesting project would be to use the 9x9 Gaussian mask to detect
  3174. the edges of large objects and use these edges to enhance the input image.
  3175. Some new code incorporates these edge detectors into the C Image Processing
  3176. System (CIPS). Listing 3 shows the changes to the main program file cips.c. I
  3177. modified case 8 to include the new types of edge detection (detect_type == 5,
  3178. 6, 7, or 8). I also added case 9 for edge enhancement. The last part of
  3179. Listing 3 shows the new version of show_menu that allows the user to select
  3180. case 8 or 9.
  3181. Listing 4 shows the new version of get_edge_options (file edge.c from Part 5).
  3182. The new version allows you to select from among eight available edge
  3183. detectors. You can select thresholding, specify a threshold value, and specify
  3184. 7x7 or 9x9 Gaussian edge detection.
  3185. Listing 4 shows the new version of the mainedge application program discussed
  3186. in Part 5. You can run an edge detector over all of the 100x100 sections of an
  3187. image without interacting with CIPS; instead mainedge uses command-line
  3188. parameters. There are seven command-line parameters -- I can never remember
  3189. the correct order. Just type mainedge <return> and the program will remind you
  3190. of the order. mainedge loops over the 100x100 blocks and calls the specified
  3191. edge detector function. The edge detector does the rest.
  3192. Try these edge detectors and do a few of the projects. There a countless
  3193. combinations to try and you can no doubt invent an edge detector tailored for
  3194. your application.
  3195. References
  3196. 1. "Recognizing Objects in a Natural Environment: A Contextual Vision System
  3197. (CVS)," Martin A. Fischler, Thomas M. Strat, Proceedings Image Understanding
  3198. Workshop, pp. 774-796, Morgan Kaufman Publishers, May 1989.
  3199. 2. Digital Image Processing, Kenneth R. Castleman, Prentice-Hall, 1979.
  3200. 3. Vision in Man and Machine, Martin D. Levine, McGraw-Hill, 1985.
  3201. 4. Contrast Based Edge Detection, R.P. Johnson, Pattern Recognition, Vol. 23,
  3202. No. 3/4, pp. 311-318, 1990.
  3203. Figure 1
  3204. Area 1:
  3205.  
  3206.  
  3207.  1 2 3
  3208.  4 5 6
  3209.  7 8 9
  3210.  
  3211.  Output of homogeneity edge detector is:
  3212.  max of {
  3213.  5 - 1 5 - 2 5 - 3 
  3214.  
  3215.  5 - 4 5 - 6 5 - 7 
  3216.  
  3217.  5 - 8 5 - 9 
  3218.  } = 4
  3219.  
  3220.  
  3221. Area 2:
  3222.  
  3223.  10 10 10
  3224.  10 10 10
  3225.  10 10 1
  3226.  
  3227.  Output of homogeneity edge detector is:
  3228.  max of {
  3229.  10 - 10 10 - 10 10 - 10 
  3230.  
  3231.  10 - 10 10 - 10 10 - 10 
  3232.  
  3233.  10 - 10 10 - 1 
  3234.  } = 9
  3235.  
  3236.  
  3237. Area 3:
  3238.  
  3239.  10 5 3
  3240.  10 5 3
  3241.  10 5 3
  3242.  
  3243.  Output of homogeneity edge detector is:
  3244.  max of {
  3245.  5 - 10 5 - 5 5 - 3 
  3246.  
  3247.  5 - 10 5 - 3 5 - 10 
  3248.  
  3249.  5 - 5 5 - 3 
  3250.  } = 5
  3251.  
  3252. Figure 2
  3253. Area 1:
  3254.  
  3255.  1 2 3
  3256.  4 5 6
  3257.  7 8 9
  3258.  
  3259.  Output of difference edge detector is:
  3260.  max of {
  3261.  1 - 9 7 - 3 
  3262.  
  3263.  4 - 6 2 - 8 
  3264.  } = 8
  3265.  
  3266.  
  3267.  
  3268. Area 2:
  3269.  
  3270.  10 10 10
  3271.  10 10 10
  3272.  10 10 1
  3273.  
  3274.  Output of difference edge detector is:
  3275.  max of {
  3276.  10 - 1 10 - 10 
  3277.  
  3278.  10 - 10 10 - 10 
  3279.  } = 9
  3280.  
  3281.  
  3282. Area 3:
  3283.  
  3284.  10 5 3
  3285.  10 5 3
  3286.  10 5 3
  3287.  
  3288.  Output of difference edge detector is:
  3289.  max of {
  3290.  10 - 3 10 - 3 
  3291.  
  3292.  10 - 3 5 - 5 
  3293.  } = 7
  3294.  
  3295. Figure 3
  3296. 7x7 mask
  3297.  0 0 -1 -1 -1 0 0
  3298.  0 -2 -3 -3 -3 -2 0
  3299.  -1 -3 5 5 5 -3 -1
  3300.  -1 -3 5 16 5 -3 -1
  3301.  -1 -3 5 5 5 -3 -1
  3302.  0 -2 -3 -3 -3 -2 0
  3303.  0 0 -1 -1 -1 0 0
  3304.  
  3305. 9x9 mask
  3306.  0 0 0 -1 -1 -1 0 0 0
  3307.  0 -2 -3 -3 -3 -3 -3 -2 0
  3308.  0 -3 -2 -1 -1 -1 -2 -3 0
  3309.  -1 -3 -1 9 9 9 -1 -3 -1
  3310.  -1 -3 -1 9 19 9 -1 -3 -1
  3311.  -1 -3 -1 9 9 9 -1 -3 -1
  3312.  0 -3 -2 -1 -1 -1 -2 -3 0
  3313.  0 -2 -3 -3 -3 -3 -3 -2 0
  3314.  0 0 0 -1 -1 -1 0 0 0
  3315.  
  3316. Figure 4
  3317. 7x7 area with lines
  3318.  
  3319.  0 10 0 10 0 10 0
  3320.  0 0 0 0 0 0 0
  3321. 10 0 10 0 10 0 10
  3322.  0 0 0 0 0 0 0
  3323.  0 10 0 10 0 10 0
  3324.  0 0 0 0 0 0 0
  3325.  
  3326. 10 0 10 0 10 0 10
  3327.  
  3328. result of convolution with 7x7 mask = 40
  3329.  
  3330.  
  3331. 9x9 area with lines
  3332.  0 0 0 0 0 0 0 0 0
  3333. 10 0 10 0 10 0 10 0 10
  3334.  0 0 0 0 0 0 0 0 0
  3335.  0 10 0 10 0 10 0 10 0
  3336.  0 0 0 0 0 0 0 0 0
  3337. 10 0 10 0 10 0 10 0 10
  3338.  0 0 0 0 0 0 0 0 0
  3339.  0 10 0 10 0 10 0 10 0
  3340.  0 0 0 0 0 0 0 0 0
  3341.  
  3342. result of convolution with 9x9 mask = -20
  3343.  
  3344. Figure 5
  3345. Edge Detector Mask
  3346.  -1 0 -1
  3347.  0 4 0
  3348.  -1 0 -1
  3349.  
  3350. Edge in well lit area
  3351.  
  3352.  50 100 100
  3353.  50 100 100 convolution with edge mask yields:
  3354.  50 100 100 400 - 50 - 50 - 100 - 100 = 100
  3355.  
  3356. Edge in poorly lit area
  3357.  
  3358.  5 10 10
  3359.  5 10 10 convolution with edge mask yields:
  3360.  5 10 10 40 - 5 - 5 - 10 - 10 = 10
  3361.  
  3362.  
  3363. Smoothing mask
  3364.  
  3365.  1 1 1
  3366.  1/9 * 1 1 1
  3367.  1 1 1
  3368.  
  3369. convolution of smoothing mask with edge in well lit area
  3370. yields:
  3371. 50+50+50+100+100+100+100+100+100 / 9 = 750/9 = 83
  3372.  
  3373. convolution of smoothing mask with edge in poorly lit
  3374. area yields:
  3375. 5+5+5+10+10+10+10+10+10 / 9 = 75/9 = 8
  3376.  
  3377. dividing original convolution by the smoothing mask
  3378. result:
  3379.  
  3380. edge in well lit area: 100 / 83 = 1
  3381.  
  3382. edge in poorly lit area: 10 / 8 = 1
  3383.  
  3384. Photo 1 House Image
  3385.  
  3386. Photo 2 Result of Homogeneity Edge Detector
  3387. Photo 3 Result of Difference Edge Detector
  3388. Photo 4 Result of Narrow Gaussian Edge Detector
  3389. Photo 5 Result of Wide Gaussian Edge Detector
  3390. Photo 6 Result of Quick Edge Detector
  3391. Photo 7 Result of Contrast-Based Edge Detector
  3392. Photo 8 Result of Edge Enhancement
  3393.  
  3394. Listing 1 (edge2.c)
  3395. /***************************************************
  3396. *
  3397. * Functions: This file contains
  3398. * homogeneity
  3399. * difference_edge
  3400. * contrast_edge
  3401. *
  3402. * Purpose:
  3403. * These functions implement several
  3404. * types of advanced edge detection.
  3405. *
  3406. * External Calls:
  3407. * wtiff.c - does_not_exist
  3408. * round_off_image_size
  3409. * create_allocate_tiff_file
  3410. * write_array_into_tiff_image
  3411. * tiff.c - read_tiff_header
  3412. * rtiff.c- read_tiff_image
  3413. * numcvrt.c - get_integer
  3414. * edge.c - fix_edges
  3415. *
  3416. * Modifications:
  3417. * 26 March 1991 - created
  3418. *
  3419. *****************************************************/
  3420.  
  3421.  
  3422.  
  3423. #include "d:\cips\cips.h"
  3424.  
  3425.  
  3426.  
  3427. short e_mask[3][3] = {
  3428. {-9, 0, -9}
  3429. { 0, 36, 0},
  3430. {-9, 0, -9} };
  3431.  
  3432. short contrast[3][3] = {
  3433. { 1, 1, 1},
  3434. { 1, 1, 1},
  3435. { 1, 1, 1}};
  3436.  
  3437.  
  3438. /**************************************************
  3439. *
  3440. * homogeneity(...
  3441. *
  3442. * This function performs edge detection by looking
  3443. * for the absence of an edge. The center of a
  3444. * 3x3 area is replaced by the absolute value of
  3445.  
  3446. * the max difference between the center point
  3447. * and its 8 neighbors.
  3448. *
  3449. *****************************************************/
  3450.  
  3451.  
  3452. homogeneity(in_name, out_name, the_image, out_image,
  3453. il, ie, ll, le, threshold, high)
  3454. char in_name[], out_name[];
  3455. int high, il, ie, ll, le, threshold;
  3456. short the_image[ROWS][COLS], out_image[ROWS][COLS];
  3457. {
  3458. int a, b, absdiff, absmax, diff, i, j,
  3459. length, max, max_diff, new_hi, new_low, width;
  3460.  
  3461. struct tiff_header_struct image_header;
  3462.  
  3463. if(does_not exist(out_name)){
  3464. printf("\n\nHOMO> output file does not exist %s",
  3465. out_name);
  3466.  
  3467. read_tiff_header(in_name, &image_header);
  3468. round_off_image_size(&image_header,
  3469. &length, &width);
  3470. image_header.image_length = length*ROWS;
  3471. image_header.image_width = width*COLS;
  3472. create_allocate_tiff_fi1e(out_name, &image_header,
  3473. out_image);
  3474. } /* ends if does_not_exist */
  3475.  
  3476. read_tiff_header(in_name, &image_header);
  3477.  
  3478. new_hi = 250;
  3479. new_low = 16;
  3480. if(image_header.bits_per_pixel == 4){
  3481. new_hi = 10;
  3482. new_low = 3;
  3483. }
  3484.  
  3485. max = 255;
  3486. if(image_header.bits_per_pixel == 4)
  3487. max = 16;
  3488. read_tiff_image(in_name, the_image, il, ie, ll, le);
  3489.  
  3490. for(i=0; i<ROWS; i++)
  3491. if((i%10) == 0) printf("%3d", i);
  3492. for(j=0; j<COLS; j++)
  3493. out_image[i][j] = 0;
  3494.  
  3495. for(i=1; i<ROWS-1; i++){
  3496. for(j=1; j<COLS-1; j++){
  3497.  
  3498. max_diff = 0;
  3499. for(a=-1; a<=1; a++){
  3500. for(b=-1; b<=1; b++){
  3501.  
  3502. diff = the_image[i][j] - the_image[i+a][j+b];
  3503. absdiff = abs(diff);
  3504. if(absdiff > max_diff) max_diff = absdiff;
  3505.  
  3506.  
  3507. } /* ends loop over b */
  3508. } /* ends loop over a */
  3509.  
  3510. out_image[i][j] = max_diff;
  3511. } /* ends loop over j */
  3512. } /* ends loop over i */
  3513.  
  3514.  
  3515. /* if desired, threshold the output image */
  3516. if(threshold == 1){
  3517. for(i=0; i<ROWS; i++){
  3518. for(j=0; j<COLS; j++){
  3519. if(out_image[i][j] > high){
  3520. out_image[i][j] = new_hi;
  3521. }
  3522. else{
  3523. out_image[i][j] = new_low;
  3524. }
  3525. }
  3526. }
  3527. } /* ends if threshold == 1 */
  3528.  
  3529. fix_edges(out_image, 1);
  3530.  
  3531. write_array_into_tiff_image(out_name, out_image,
  3532. il, ie, ll, le);
  3533.  
  3534. } /* ends homogeneity */
  3535.  
  3536. /**************************************************
  3537. *
  3538. * difference_edge(...
  3539. *
  3540. * This function performs edge detection by looking
  3541. * at the differences in the pixels that surround
  3542. * the center point of a 3x3 area. It replaces the
  3543. * center point with the absolute value of the
  3544. * max difference of:
  3545. * upper left - lower right
  3546. * upper right - lower left
  3547. * left - right
  3548. * top - bottom
  3549. *
  3550. *********************************************/
  3551.  
  3552. difference_edge(in_name, out_name, the_image, out_image,
  3553. il, ie, ll, le, threshold, high)
  3554. char in_name[], out_name[];
  3555. int high, il, ie, ll, le, threshold;
  3556. short the_image[ROWS][COLS], out_image[ROWS][COLS];
  3557. }
  3558. int a, b, absdiff, absmax, diff, i, j,
  3559. length, max, max_diff, new_hi, new_low, width;
  3560.  
  3561. struct tiff_header_struct image_header;
  3562.  
  3563.  
  3564. if(does_not_exist(out_name)){
  3565.  
  3566. printf("\n\nDIFF> output file does not exist %s",
  3567. out_name)
  3568. read_tiff_header(in_name, &image_header);
  3569. round_off_image_size(&image_header,
  3570. &length, &width);
  3571. image_header.image_length = length*ROWS;
  3572. image_header.image_width = width*COLS;
  3573. create_allocate_tiff_file(out_name, &image_header,
  3574. out_image);
  3575. } /* ends if does_not_exist */
  3576.  
  3577. read_tiff_header(in_name, &image_header);
  3578.  
  3579. new_hi = 250;
  3580. new_low = 16;
  3581. if(image_header.bits_per_pixel == 4){
  3582. new_hi = 10;
  3583. new_low = 3;
  3584. }
  3585.  
  3586. max = 255;
  3587. if(image_header.bits_per_pixel == 4)
  3588. max = 16;
  3589.  
  3590. read_tiff_image(in_name, the_image, il, ie, ll, le);
  3591.  
  3592. for(i=0 i<ROWS; i++)
  3593. for(j=0; j<COLS; j++)
  3594. out_image[i] [j] = 0;
  3595.  
  3596. for(i=1; i<ROWS-1; i++){
  3597. if(i%0) == 0) printf("%3d", i);
  3598. for(j=1; j<COLS-1; j++){
  3599.  
  3600. max_diff = 0;
  3601. absdiff = abs(the_image[i-1][j-1] -
  3602. the_image[i+1][j+l]);
  3603. if(absdiff > max_diff) max_diff = absdiff;
  3604.  
  3605. absdiff = abs(the_image[i-1][j+1] -
  3606. the_image[i+1][j-1]);
  3607. if(absdiff > max_diff) max_diff = absdiff;
  3608.  
  3609. absdiff = abs(the_image[i][j-l] -
  3610. the_image[i][j+1] );
  3611. if(absdiff > max_diff) max_diff = absdiff;
  3612.  
  3613. absdiff = abs(the_image[i-1][j] -
  3614. the_image[i+1][j]);
  3615. if(absdiff > max_diff) max_diff = absdiff;
  3616.  
  3617. out_image[i][j] = max_diff;
  3618.  
  3619. } /* ends loop over j */
  3620. } /* ends loop over i */
  3621.  
  3622. /* if desired, threshold the output image */
  3623. if(threshold == 1){
  3624. for(i=0; i<ROWS; i++){
  3625.  
  3626. for(j=0; j<COLS; j++){
  3627. if(out_image[i][j] > high){
  3628. out_image[i][j] = new_hi;
  3629. }
  3630. else{
  3631. out_image[i][j] = new_low;
  3632. }
  3633. }
  3634. }
  3635. } /* ends if threshold == 1 */
  3636.  
  3637.  
  3638.  
  3639. fix_edges(out_image, 1);
  3640. write_array_into_tiff_image(out_name, out_image,
  3641. il, ie, ll, le);
  3642.  
  3643.  
  3644. } /* ends difference_edge */
  3645.  
  3646.  
  3647. /*************************************************
  3648. *
  3649. * contrast_edge(...
  3650. *
  3651. * The edge detector uses the basic quick edge
  3652. * detector mask and then divides the result
  3653. * by a contrast smooth mask. This implements
  3654. * Johnson's contrast based edge detector.
  3655. *
  3656. **************************************************/
  3657.  
  3658. contrast_edge(in_name, out_name, the_image, out_image,
  3659. il, ie, ll, le, threshold, high)
  3660. char in_name[], out_name[];
  3661. int high, il, ie, ll, le, threshold;
  3662. short the_image[ROWS][COLS], out_image[ROWS][COLS];
  3663. {
  3664. int ad, d;
  3665. int a, b, absdiff, absmax, diff, i, j,
  3666. length, max, new_hi, new_low, sum_d, sum_n, width;
  3667.  
  3668. struct tiff_header_struct image_header;
  3669.  
  3670.  
  3671. if(does_not_exist(out_name)){
  3672. printf("\n\nCONTRAST> output file does not exist %s",
  3673. out_name);
  3674. read_tiff_header(in_name, &image_header);
  3675. round_off_image_size(&image_header,
  3676. &length, &width);
  3677. image_header.image_length = length*ROWS;
  3678. image_header.image_width = width*COLS;
  3679. create_allocate_tiff_file(out_name, &image_header,
  3680. out_image);
  3681. } /* ends if does_not_exist */
  3682. read_tiff_header(in_name, &image_header);
  3683.  
  3684. new_hi = 250;
  3685.  
  3686. new_low = 16;
  3687. if(image_header.bits_per_pixel == 4){
  3688. new_hi = 10;
  3689. new_low = 3;
  3690. }
  3691.  
  3692. max = 255;
  3693. if(image_header.bits_per_pixel == 4)
  3694. max = 16;
  3695.  
  3696. read_tiff_image(in_name, the_image, il, ie, ll, le);
  3697.  
  3698. for(i=0; i<ROWS; i++)
  3699. for(j=0; j<COLS; j++)
  3700. out_image[i][j] = 0;
  3701.  
  3702. for(i=1; i<ROWS-1; i++){
  3703. if((i%10) == 0) printf("%3d", i);
  3704. for(j=1; j<COLS-1; j++){
  3705.  
  3706. sum_n = 0;
  3707. sum_d = 0;
  3708.  
  3709. for(a=-1; a<2; a++){
  3710. for(b=-1; b<2; b++){
  3711. sum_n = sum_n + the_image[i+a][j+b] *
  3712. e_mask[a+1][b+1];
  3713. sum_d = sum_d + the_image[i+a][j+b] *
  3714. contrast [a+1][b+1];
  3715. }
  3716. }
  3717.  
  3718. d = sum_d / 9;
  3719. if(d ==0)
  3720. d = 1;
  3721.  
  3722. out_image[i] [j] = sum_n/d;
  3723.  
  3724. if(out_image[i][j] > max) out_image[i][j] = max;
  3725. if(out_image[i][j] < 0) out_image[i][j] = 0;
  3726.  
  3727. } /* ends loop over j */
  3728. } /* ends loop over i */
  3729.  
  3730.  
  3731. /* if desired, threshold the output image */
  3732. if(threshold == 1){
  3733. for(i=0; i<ROWS; i++){
  3734. for(j=0; j<COLS; j++){
  3735. if(out_image[i][j] > high){
  3736. out_image[i][j] = new_hi;
  3737. }
  3738. else {
  3739. out_image[i] [j] = new low;
  3740. }
  3741. }
  3742. }
  3743. } /* ends if threshold == 1 */
  3744.  
  3745.  
  3746.  
  3747. fix_edges(out_image, 1);
  3748. write_array_into_tiff_image(out_name, out_image,
  3749. il, ie, ll, le);
  3750.  
  3751. } /* ends contrast_edge */
  3752.  
  3753. /* End of File */
  3754.  
  3755.  
  3756. Listing 2 (edge3.c)
  3757. /***********************************************
  3758. *
  3759. * file d:\cips\edge3.c
  3760. *
  3761. * Functions: This file contains
  3762. * gaussian_edge
  3763. * enhance_edges
  3764. *
  3765. * Purpose:
  3766. * These functions implement several
  3767. * types of advanced edge detection.
  3768. *
  3769. * External Calls:
  3770. * wtiff.c - does_not_exist
  3771. * round_off_image_size
  3772. * create_allocate_tiff_file
  3773. * write_array_into_tiff_image
  3774. * tiff.c-read tiff header
  3775. * rtiff.c-read_tiff_image
  3776. * numcvrt.c-get_integer
  3777. * edge.c-fix_edges
  3778. *
  3779. * Modifications:
  3780. * 26 March 1991 - created
  3781. *
  3782. ***********************************************/
  3783.  
  3784.  
  3785. #include "d:\cips\cips.h"
  3786.  
  3787.  
  3788. short enhance_mask[3][3] = {
  3789. {-1, 0, -1},
  3790. { 0, 4, 0},
  3791. {-1, 0, -1} };
  3792.  
  3793.  
  3794. short g7[7][7] = {
  3795. { 0, 0, -1, -1, -1, 0, 0},
  3796. { 0, -2, -3, -3, -3, -2, 0},
  3797. { -1, -3, 5, 5, 5, -3, -1},
  3798. { -1, -3, 5, 16, 5, -3, -1},
  3799. { -1, -3, 5, 5, 5, -3, -1},
  3800. { 0, -2, -3, -3, -3, -2, 0},
  3801. { 0, 0, -1, -1, -1, 0, 0}};
  3802.  
  3803. short g9[9] [9] = {
  3804. { 0, 0, 0, -1, -1, -1, 0, 0, 0},
  3805.  
  3806. { 0, -2, -3, -3, -3, -3, -3, -2, 0},
  3807. { 0, -3, -2, -1, -1, -1, -2, -3, 0},
  3808. { -1, -3, -1, 9, 9, 9, -1, -3, -1},
  3809. { -1, -3, -1, 9, 19, 9, -1, -3, -1},
  3810. { -1, -3, -1, 9, 9, 9, -1, -3, -1},
  3811. { 0, -3, -2, -1, -1, -1, -2, -3, 0},
  3812. { 0, -2, -3, -3, -3, -3, -3, -2, 0},
  3813. { 0, 0, 0, -1, -1, -1, 0, 0, 0}};
  3814.  
  3815.  
  3816.  
  3817. /******************************************************************
  3818. *
  3819. * gaussian_edge(...
  3820. *
  3821. *
  3822. *******************************************************************/
  3823.  
  3824. gaussian_edge(in_name, out_name, the_image, out_image,
  3825. il, ie, ll, le, size, threshold, high)
  3826. char in_name[], out_name[];
  3827. int high, il, ie, ll, le, size, threshold;
  3828. short the_image[ROWS] [COLS], out_image[ROWS] [COLS];
  3829. {
  3830.  
  3831. char response[80];
  3832. long sum;
  3833. int a, b, absdiff, absmax, diff, i, j,
  3834. length, lower, max, new_hi, new_low,
  3835. scale, start, stop, upper, width;
  3836.  
  3837. struct tiff_header_struct image_header;
  3838.  
  3839. if(does_not_exist(out_name)){
  3840. printf("\n\nGAUSSIAN> output file does not exist %s",
  3841. out_name);
  3842. read_tiff_header(in_name,&image_header);
  3843. round_off_image_size(&image_header,
  3844. &length, &width);
  3845. image_header.image_length = length*ROWS;
  3846. image_header.image_width = width*COLS;
  3847. create_allocate_tiff_file(out_name, &image_header,
  3848. out_image);
  3849. } /* ends if does_not_exist */
  3850.  
  3851. read_tiff_header(in_name, &image_header);
  3852.  
  3853. new_hi = 250;
  3854. new_low = 16;
  3855. if(image_header.bits_perpixel == 4){
  3856. new_hi = 10;
  3857. new_low = 3;
  3858. }
  3859.  
  3860. max = 255;
  3861. if(image_header.bits_per_pixel == 4)
  3862. max = 16;
  3863.  
  3864. if(size == 7){
  3865.  
  3866. lower = -3;
  3867. upper = 4;
  3868. start = 3;
  3869. stop = ROWS-3;
  3870. scale = 2;
  3871. }
  3872.  
  3873. if(size == 9){
  3874. lower = -4;
  3875. upper = 5;
  3876. start = 4;
  3877. stop = ROWS-4;
  3878. scale = 2;
  3879. }
  3880.  
  3881.  
  3882. read_tiff_image(in_name, the_image, il, ie, ll, le);
  3883.  
  3884. for(i=0; i<ROWS; i++)
  3885. for(j=0; j<COLS; j++)
  3886. out_image[i] [j] = 0;
  3887.  
  3888.  
  3889. for(i=start; i<stop; i++){
  3890. if ((i%10) == 0) printf(" i=%d", i);
  3891. for(j=start; j<stop; j++){
  3892.  
  3893. sum = 0;
  3894.  
  3895. for(a=lower; a<upper; a++){
  3896. for(b=lower; b<upper; b++){
  3897. if(size == 7)
  3898. sum = sum + the_image[i+a] [j+b] *
  3899. g7 [a+3] [b+3];
  3900. if(size == 9)
  3901. sum = sum + the_image[i+a] [j+b] *
  3902. g9 [a+4] [b+4];
  3903. } /* ends loop over a */
  3904. } /* ends loop over b */
  3905.  
  3906. if(sum < 0) sum = 0;
  3907. if(sum > max) sum = max;
  3908. out_image[i] [j] = sum;
  3909.  
  3910. } /* ends loop over j */
  3911. } /* ends loop over i */
  3912.  
  3913. /* if desired, threshold the output image */
  3914. if(threshold == 1){
  3915. for(i=0; i<ROWS; i++){
  3916. for(j=0; j<COLS; j++){
  3917. if(out_image[i] [j] > high){
  3918. out_image[i][j] = new_hi;
  3919. }
  3920. else{
  3921. out_image[i] [j] = new_low;
  3922. }
  3923. }
  3924. }
  3925.  
  3926. } /* ends if threshold == 1 */
  3927.  
  3928.  
  3929. fix_edges(out_image, size/2);
  3930.  
  3931. write_array_into_tiff_image(out_name, out_image,
  3932. il, ie, ll, le);
  3933.  
  3934. } /* ends gaussian_edge */
  3935.  
  3936.  
  3937. /*********************************************
  3938. *
  3939. * enhance_edges(...
  3940. *
  3941. * This function enhances the edges in an
  3942. * input image and writes the enhanced
  3943. * result to an output image. It operates
  3944. * much the same way as detect_edges
  3945. * except it uses only one type of mask.
  3946. *
  3947. * The threshold and high parameters perform
  3948. * a different role in this function. The
  3949. * threshold parameter does not exist. The
  3950. * high parameter determines if the edge is
  3951. * strong enough to enhance or change the
  3952. * input image.
  3953. *
  3954. *******************************************/
  3955.  
  3956.  
  3957. enhance_edges(in_name, out_name, the_image, out_image,
  3958. il, ie, ll, le, high)
  3959. char in_name[], out_name[];
  3960. int high, il, ie, ll, le;
  3961. short the_image[ROWS][COLS], out_image[ROWS][COLS];
  3962.  
  3963. {
  3964. int a, b, i, j, k,
  3965. length, max, new_hi,
  3966. new_lo, sum, width;
  3967. struct tiff_header_struct image_header;
  3968.  
  3969.  
  3970. if(does_not_exist(out_name)){
  3971. printf("\n\nEE> output file does not exist %s",
  3972. out_name);
  3973. read_tiff_header(in_name, &image_header);
  3974. round_off_image_size(&image_header,
  3975. &length, &width);
  3976. image_header.image_length = length*ROWS;
  3977. image_header.image_width = width*COLS;
  3978. create_allocate_tiff_file(out_name, &image_header,
  3979. out_image);
  3980. } /* ends if does_not_exist */
  3981.  
  3982. read_tiff_header(in_name, &image_header);
  3983.  
  3984. max = 255;
  3985.  
  3986.  
  3987. if(image_header.bits_per_pixel == 4)
  3988. max = 16;
  3989.  
  3990.  
  3991. read_tiff_image(in_name, the_image, il, ie, ll, le);
  3992.  
  3993. /* Do convolution over image array */
  3994. for(i=1; i<ROWS-1; i++){
  3995. if((i%10) == 0) printf("%d ", i);
  3996. for(j=1; j<COLS-1; j++){
  3997. sum = 0;
  3998. for(a=-1; a<2; a++){
  3999. for(b=-1; b<2; b++){
  4000. sum = sum +
  4001. the_image[i+a] [j+b] *
  4002. enhance_mask [a+1] [b+1];
  4003.  
  4004. }
  4005. }
  4006. if(sum < 0) sum = 0;
  4007. if(sum > max) sum = max;
  4008. if(sum > high)
  4009. out_image[i] [j] = max;
  4010. else
  4011. out_image[i][j] = the_image[i][j];
  4012. } /* ends loop over j */
  4013. } /* ends loop over i */
  4014.  
  4015. fix_edges(out_image, 1);
  4016.  
  4017.  
  4018. write_array_into_tiff_image(out_name, out_image,
  4019. il, ie, ll, le);
  4020. } /* ends enhance_edges */
  4021. /* End of File */
  4022.  
  4023.  
  4024. Listing 3
  4025. case 8: /* perform edge detection */
  4026. printf("\nCIPS> Enter input image name\n");
  4027. get_image_name(name);
  4028. printf("\nCIPS> Enter output image name\n");
  4029. get_image_name(name2);
  4030. get_parameters(&il, &ie, &ll, &le);
  4031. get_edge_options(&detect_type, &detect_threshold,
  4032. &high, &size);
  4033. if(detect_type == 1 
  4034. detect_type == 2 
  4035. detect_type == 3)
  4036. detect_edges(name, name2, the_image, out_image,
  4037. il, ie, ll, le, detect_type,
  4038. detect_threshold, high);
  4039. if(detect_type == 4)
  4040. quick_edge(name, name2, the_image, out_image,
  4041. il, ie, ll, le, detect_threshold,
  4042. high);
  4043. if(detect_type == 5)
  4044. homogeneity(name, name2, the_image, out_image,
  4045.  
  4046. il, ie, ll, le, detect_threshold,
  4047. high);
  4048. if(detect_type == 6)
  4049. difference_edge(name, name2, the_image, out_image,
  4050. il, ie, ll, le, detect_threshold,
  4051. high);
  4052. if(detect_type == 7)
  4053. contrast_edge(name, name2, the_image, out_image,
  4054. il, ie, ll, le, detect_threshold,
  4055. high);
  4056. if(detect_type == 8)
  4057. gaussian_edge(name, name2, the_image, out_image,
  4058. il, ie, ll, le, size,
  4059. detect_threshold, high);
  4060. break;
  4061.  
  4062. case 9: /* edge enhancement */
  4063. printf("\nCIPS> Enter input image name\n");
  4064. get_image_name(name);
  4065. printf("\nCIPS> Enter output image name\n");
  4066. get_image_name(name2);
  4067. get_parameters(&il, &ie, &ll, &le);
  4068. printf("\nCIPS> Enter high threshold parameter");
  4069. printf(" \n\t__\b\b\b");
  4070. get_integer(&high);
  4071. enhance_edges(name, name2, the_image, out_image,
  4072. il, ie, ll, le, high);
  4073. break;
  4074.  
  4075. .
  4076. .
  4077. .
  4078.  
  4079. /**************************************************
  4080. *
  4081. * show_menu(..
  4082. *
  4083. * This function displays the CIPS main menu.
  4084. *
  4085. ***************************************************/
  4086. show_menu()
  4087. {
  4088.  
  4089. printf("\n\n\nWelcome to CIPS");
  4090. printf("\nThe C Image Processing System");
  4091. printf("\nThese are you choices:\n");
  4092. printf("\n\t1. Display image header");
  4093. printf("\n\t2. Show image numbers");
  4094. printf("\n\t3. Print image numbers");
  4095. printf("\n\t4. Display image (VGA & EGA only)");
  4096. printf("\n\t5. Display or print image using halftoning");
  4097. printf("\n\t6. Print graphics image using dithering");
  4098. printf("\n\t7. Print or display histogram numbers");
  4099. printf("\n\t8. Perform edge detection");
  4100. printf("\n\t9. Perform edge enhancement");
  4101. printf("\n\t20. Exit system");
  4102. printf("\n\nEnter choice _\b");
  4103.  
  4104. } /* ends show_menu */
  4105.  
  4106.  
  4107. /* End of File */
  4108.  
  4109.  
  4110. Listing 4
  4111. /******************************************************
  4112. *
  4113. * get_edge_options(...
  4114. *
  4115. * This function queries the user for the
  4116. * parameters need to perform edge
  4117. * detection.
  4118. *
  4119. *****************************************************/
  4120.  
  4121.  
  4122. get_edge_options(detect_type, threshold, high, size)
  4123. int *detect_type, *high, *size, *threshold;
  4124. {
  4125. int not_finished, response;
  4126. not_finished = 1;
  4127. while(not_finished){
  4128.  
  4129. printf("\nThe Edge Detector options are:\n");
  4130. printf("\n\t1. Type of edge detector is %d", *detect_type);
  4131. printf("\n\t (recall 1=Prewitt 2=Kirsch");
  4132. printf("\n\t 3=Sobel 4=quick");
  4133. printf("\n\t 5=homegeneity 6=difference");
  4134. printf("\n\t 7=contrast 8=gaussian");
  4135. printf("\n\2. Threshold output is %d (0=off 1=on)", *threshold);
  4136. printf("\n\t3. High threshold is %d", *high);
  4137. printf("\n\t4. Size is %d (gaussian only)", *size);
  4138. printf("\n\nEnter choice (0 = no change) _\b");
  4139.  
  4140.  
  4141. get_integer(&response);
  4142.  
  4143. if(response == 0){
  4144. not_finished = 0;
  4145. }
  4146.  
  4147.  
  4148. if(response == 1){
  4149. printf("\n\nEnter type of edge detector");
  4150. printf("\n\t (recall 1=Prewitt 2=Kirsch");
  4151. printf("\n\t 3=Sobel 4=quick");
  4152. printf("\n\t 5=homogeneity 6=difference");
  4153. printf("\n\t 7=contrast 8=gaussian");
  4154. printf("\n _\b");
  4155. get_integer(detect_type);
  4156. }
  4157.  
  4158. if(response == 2){
  4159. printf("\n\nEnter threshold output (0=off 1=on)");
  4160. printf("\n _\b");
  4161. get_integer(threshold);
  4162. }
  4163.  
  4164. if(response == 3){
  4165.  
  4166. printf("\n\nEnter high threshold");
  4167. printf("\n _\b");
  4168. get_integer(high);
  4169. }
  4170.  
  4171. if(response == 4){
  4172. printf("\n\nEnter size for gaussian (7 or 9)");
  4173. printf("\n _\b");
  4174. get_integer(size);
  4175. }
  4176. } /* ends while not_finished */
  4177.  
  4178. } /* ends get_edge_options */
  4179. /* End of File */
  4180.  
  4181.  
  4182. Listing 5 (mainedge.c)
  4183. /***********************************************
  4184. *
  4185. * Purpose:
  4186. * This file contains the main calling
  4187. * routine in an edge detection program.
  4188. *
  4189. * External Calls:
  4190. * gin.c - get_image_name
  4191. * numcvrt.c - get_integer
  4192. * int convert
  4193. * tiff.c - read_tiff_header
  4194. * edge.c - quick_edge
  4195. * detect_edges
  4196. * edge2.c - homogeneity
  4197. * difference_edge
  4198. * contrast_edge
  4199. * edge3.c - gaussian_edge
  4200. * enhance_edges
  4201. *
  4202. *
  4203. * Modifications:
  4204. * 2 February 1991 - created
  4205. *
  4206. ***********************************************/
  4207.  
  4208. #include "d:\cips\cips.h"
  4209.  
  4210.  
  4211.  
  4212. short the_image[ROWS][COLS];
  4213. short out_image[ROWS][COLS];
  4214.  
  4215. main(argc, argv)
  4216. int argc;
  4217. char *argv[];
  4218. {
  4219.  
  4220. char name[80], name2[80];
  4221.  
  4222. int count, i, ie, il, j, le, length, ll, size,
  4223. t, type, v, width;
  4224.  
  4225.  
  4226.  
  4227. struct tiff_header_struct image_header;
  4228.  
  4229. _setvideomode(_TEXTC80); /* MSC 6.0 statements */
  4230. _setbkcolor(1);
  4231. _settextcolor(7);
  4232. _clearscreen(_GCLEARSCREEN);
  4233.  
  4234. if(argc < 7){
  4235. printf("\n\nNot enough parameters:");
  4236. printf("\n");
  4237. printf("\n usage: mainedge in-file out-file type ");
  4238. printf(" threshold? threshold-value size");
  4239. printf("\n recall type: 1-Prewitt 2-Kirsch 3-Sobel");
  4240. printf("\n 4-quick 5-homogeneity 6-difference");
  4241. printf("\n 7-contrast 8-gaussian 9-enhance");
  4242. printf("\n threshold? 1-threshold on 2-threshold off\n");
  4243. exit(0);
  4244. }
  4245.  
  4246. strcpy(name, argv[1]);
  4247. strcpy(name2, argv[2]);
  4248. int_convert(argv[3], &type);
  4249. int_convert(argv[4], &t);
  4250. int_convert(argv[5], &v);
  4251. int_convert(argv[6], &size);
  4252.  
  4253. il = 1;
  4254. ie = 1;
  4255. ll = ROWS+1;
  4256. le = COLS+1;
  4257.  
  4258. read_tiff_header(name, &image_header);
  4259.  
  4260. length = (90 + image_header.image_length)/ROWS;
  4261. width = (90 +image_header.image_width)/COLS;
  4262. count = 1;
  4263. printf("\nlength=%d width=%d", length, width);
  4264.  
  4265. for(i=0; i<length; i++){
  4266. for(j=0; j<width; j++){
  4267. printf("\nrunning %d of %d", count, length*width);
  4268. count++;
  4269. if(type == 9)
  4270. enhance_edges(name, name2, the_image, out_image,
  4271. il+i*ROWS, ie+j*COLS, ll+i*ROWS,
  4272. le+j*COLS, v);
  4273. if(type == 8)
  4274. gaussian_edge(name, name2, the_image, out_image,
  4275. il+i*ROWS, ie+j*COLS, ll+i*ROWS,
  4276. le+j*COLS, size, t, v);
  4277. if(type == 7)
  4278. contrast_edge(name, name2, the_image, out_image,
  4279. il+i*ROWS, ie+j*COLS, ll+i*ROWS,
  4280. le+j*COLS, t, v);
  4281. if(type == 6)
  4282. difference_edge(name, name2, the_image, out_image,
  4283. il+i*ROWS, ie+j*COLS, ll+i*ROWS,
  4284. le+j*COLS, t, v);
  4285.  
  4286. if(type == 5)
  4287. homogeneity(name, name2, the_image, out_image,
  4288. il+i*ROWS, ie+j*COLS, ll+i*ROWS,
  4289. le+j*COLS, t, v);
  4290. if(type == 4)
  4291. quick_edge(name, name2, the_image, out_image,
  4292. il+i*ROWS, ie+j*COLS, ll+i*ROWS,
  4293. le+j*COLS, t, v);
  4294. if(type == 3 type == 2 type == 1)
  4295. detect_edges(name, name2, the_image, out_image,
  4296. il+i*ROWS, ie+j*COLS, ll+i*ROWS,
  4297. le+j*COLS, type, t, v);
  4298. }
  4299. }
  4300.  
  4301. } /* ends main */
  4302.  
  4303. /* End of File */
  4304.  
  4305.  
  4306.  
  4307.  
  4308.  
  4309.  
  4310.  
  4311.  
  4312.  
  4313.  
  4314.  
  4315.  
  4316.  
  4317.  
  4318.  
  4319.  
  4320.  
  4321.  
  4322.  
  4323.  
  4324.  
  4325.  
  4326.  
  4327.  
  4328.  
  4329.  
  4330.  
  4331.  
  4332.  
  4333.  
  4334.  
  4335.  
  4336.  
  4337.  
  4338.  
  4339.  
  4340.  
  4341.  
  4342.  
  4343.  
  4344.  
  4345.  
  4346.  
  4347.  
  4348.  
  4349. A Practical Use For Multiple Threads
  4350.  
  4351.  
  4352. Steve Halladay and Mike Wiebel
  4353.  
  4354.  
  4355. Steve Halladay and Mike Wiebel currently work for Storage-Tek in Louisville,
  4356. CO. Steve and Mike have over ten years of multi-threaded programming
  4357. experience. Steve holds an M.S. in computer science from Brigham Young
  4358. University. He is also the author of the multi-threaded software Multi-C and
  4359. CSIM. Mike has a B.S. in computer science from Colorado State University. You
  4360. can contact Steve or Mike at (303) 673-6683.
  4361.  
  4362.  
  4363. Though trade journals have given significant attention to multi-threaded
  4364. programming, especially the use of C's setjmp and longjmp facilities, there
  4365. remains a great deal of confusion in the practical application of
  4366. multi-threaded techniques. In fact, the examples used to demonstrate
  4367. multi-threaded functionality are often contrived or unrealistic.
  4368. Multi-threaded programming can in fact be applied to everyday programming
  4369. problems. This article presents one such problem and shows how multi-threaded
  4370. programming simplifies the program solution.
  4371.  
  4372.  
  4373. Parallel Recursive Data Structure Access
  4374.  
  4375.  
  4376. Several years ago we undertook the building of a large text retrieval system.
  4377. The retrieval system would read the text of documents and classify them in
  4378. terms of subject matter. The process of reading through the documents was
  4379. processor intensive and normally run in batches after hours.
  4380. As the retrieval system reads the text, it creates a recursive data structure
  4381. representing the subject matter of each document. After reading a batch of
  4382. documents, the system would traverse all the data structures to write them to
  4383. disk as though they were a single virtual data structure.
  4384. The nature of the structure made it desirable to use recursion to traverse the
  4385. data structure. Recursion, however, would prevent traversing a single data
  4386. structure concurrently with the others. We combined multi-threaded constructs
  4387. with a simple recursive algorithm to traverse many data structures
  4388. concurrently.
  4389. Consider, for example, the problem of concurrently traversing multiple binary
  4390. trees. An individual binary tree is a data structure that lends itself to
  4391. recursive traversal. However, multiple binary trees make using recursion
  4392. difficult. In Figure 1, the pre-order traversal of the first binary tree
  4393. results in visiting nodes 1, 5, 7 and 9. Pre-order traversal of the second
  4394. binary tree would visit 3, 6, 8 and 10. To traverse both trees as if they were
  4395. one virtual tree, you would want to visit the nodes as follows: 1, 3, 5, 6, 7,
  4396. 8, 9 and 10.
  4397.  
  4398.  
  4399. The Single Threaded Approach
  4400.  
  4401.  
  4402. The program in Listing 1 constructs multiple binary trees that contain random
  4403. values and then traverses the trees as if they were one virtual tree. Listing
  4404. 1 contains both the single threaded and multiple threaded examples. The
  4405. programmer selects the single thread version by compiling the program with the
  4406. SINGLE_THREAD macro defined.
  4407. The main function calls BuildTree to create each random binary tree. Binary
  4408. trees consist of NODEs, each of which has left and right sub-tree pointers,
  4409. and a key. Once created, the trees array has the address of the root of each
  4410. tree in the array.
  4411. BuildTree creates the random binary trees by generating random keys. BuildTree
  4412. inserts the random keys into the tree by calling AddKey.AddKey then performs a
  4413. standard recursive binary tree insertion by traversing left or right depending
  4414. upon the magnitude of the key value.
  4415. Once main creates the random binary trees, MergeTrees traverses them. In the
  4416. single threaded example, MergeTrees starts at the root node of each tree and
  4417. moves iteratively from node to node. The single threaded example first moves
  4418. to the left-most node, pushing those nodes onto a stack. The for and while
  4419. loops within MergeTrees perform the initial traversal to the left-most node of
  4420. each tree.
  4421. After the single threaded version locates the left-most node of each tree,
  4422. MergeTrees iteratively finds and prints the lowest key until it has visited
  4423. all nodes of all trees. To find the lowest key, MergeTrees calls
  4424. DisplayLowest, which compares the key of the node from each tree and prints
  4425. the lowest of the keys. Once DisplayLowest prints the lowest key, MergeTrees
  4426. calls TraverseRightSub.
  4427. TraverseRightSub locates the next lowest key within the tree. It moves as far
  4428. left as possible from the current node's right sub-tree. If no right sub-tree
  4429. exists, TraverseRightSub will at least pop the stack, causing the parent node
  4430. to be on the top of the stack. In any event, the effect of TraverseRightSub is
  4431. to leave the stack with the next lowest value on the top of the stack. This
  4432. traversal allows DisplayLowest to continue to operate as previously described.
  4433.  
  4434.  
  4435. A Multi-Threaded Example
  4436.  
  4437.  
  4438. The multi-threaded approach to traversing many concurrent binary trees is a
  4439. simple extension to traversing a standard single tree. The idea is to traverse
  4440. each tree individually using the standard recursive algorithm. A separate
  4441. thread traverses each tree. Before a node in a tree can be visited, its key
  4442. must be compared with the keys of all other trees about to be visited. The
  4443. nodes with the lower keys are visited first.
  4444. Listing 1 also contains a multi-threaded approach. The #else section of the
  4445. listing contains this example. Compiling the p rogram with the SINGLE_THREAD
  4446. macro not defined yields the multi-threaded example. This example uses the
  4447. Multi-C library, which supports portable, non-preemptive multi-threaded
  4448. constructs such as thread creation, scheduling, inter-thread communication,
  4449. etc.
  4450. The multi-threaded solution creates the random binary trees using the same
  4451. procedures as in the single threaded version. However, it uses a different
  4452. implementation of MergeTrees.
  4453. In the multi-threaded example MergeTrees creates a separate thread for each
  4454. binary tree by calling MtCCoroutine, a Multi-C function that creates a new
  4455. thread of execution. Each new thread begins executing in the PrintTree
  4456. routine. When the new thread completes the PrintTree routine, it will
  4457. terminate execution and yield the processor to other threads. The original
  4458. thread that created the sub-tree threads will continue to execute to
  4459. completion.
  4460. When the original thread completes by returning from main, the sub-tree
  4461. threads will begin execution in PrintTree, a standard recursive binary tree
  4462. traversal routine. The first thread created will be the first to start
  4463. executing. It first traverses the left sub-tree, then visits the current node
  4464. by calling PrintNode. Finally PrintTree traverses the right sub-tree.
  4465. PrintNode guarantees that the node about to be visited has the smallest key of
  4466. any of the trees' current nodes. PrintNode compares its current key to the
  4467. other trees' keys using the static variable CurrentKey. When using the Multi-C
  4468. library, each thread has its own stack, so auto variables are thread-specific.
  4469. The remainder of memory is accessible to all threads. Therefore threads share
  4470. static variables (though they are local to a procedure) and can use them as a
  4471. simple means of inter-thread communication. An alternative solution to
  4472. inter-thread communication would be to use Multi-C's thread mailboxes. Thread
  4473. mailboxes allow threads to receive information from other currently executing
  4474. threads.
  4475. CurrentKey is initialized with a number that is the largest integer so that
  4476. all keys will be less than it. As threads execute PrintNode, each thread
  4477. compares its key to CurrentKey. If the thread's key is less than CurrentKey,
  4478. the thread makes its key the CurrentKey and calls MtCYield. MtCYield is a
  4479. Multi-C library function that causes the thread to suspend execution and
  4480. allows another thread to begin. The yielding thread resumes execution when
  4481. MtCYield returns.
  4482. As threads yield, the Multi-C scheduler reschedules them for execution using a
  4483. round robin algorithm. CurrentKey will equal the thread's current key when all
  4484. other threads have executed and it is the lowest of the threads' keys. Threads
  4485. wait for this condition to occur while executing a loop within PrintNode.
  4486. PrintNode finally visits the node when the executing thread's key is equal to
  4487. the CurrentKey. Then recursion within the thread's tree continues.
  4488.  
  4489.  
  4490. Comparing Complexities
  4491.  
  4492.  
  4493. To compare the complexities of the two approaches quantitatively, we refer to
  4494. work initiated several years ago by Maurice Halstead at Purdue. Dr. Halstead
  4495. started a calculus he called "Software Science," in which he defines a measure
  4496. of program volume as:
  4497. V = N log2 n
  4498. N = N1 + N2
  4499. n = n1 + n2
  4500. Where:
  4501. V = the program volume
  4502. N = the program length
  4503. N1 = the total number of operators
  4504.  
  4505. N2 = the total number of operands
  4506. n = the vocabulary size
  4507. n1 = the unique operator count
  4508. n2 = the unique operand count
  4509. The volume program metric allows us to compare relative complexities of the
  4510. two approaches. Table 1 contains the empirical and calculated values for each
  4511. approach. While the rules used for counting operators and operands are not
  4512. necessarily the same as those used by Halstead, they are at least consistent
  4513. for both examples. We counted only the operators and operands in the code for
  4514. MergeTrees, since it contains the only differences in the two approaches.
  4515. Table 1 shows the volume for each implementation of MergeTrees. Program volume
  4516. is an estimate of effort required to understand the workings of a program. The
  4517. other metrics in Table 1 are used to calculate the program volume. The volume
  4518. of the single threaded example is slightly greater than three times the
  4519. multi-threaded example. This implies the single threaded example is three
  4520. times more complex than the multi-threaded example.
  4521. We also counted the number of lines of code for each example, shown in Table
  4522. 1, as another measure of program complexity. We defined the number of lines of
  4523. code in the examples as the total number of lines minus blank lines and lines
  4524. only containing comments. It is interesting to notice that this measure also
  4525. shows the single threaded approach is more complex by roughly three times.
  4526.  
  4527.  
  4528. Conclusion
  4529.  
  4530.  
  4531. The program described here is a tightly coupled multi-threaded program. In
  4532. such programs each thread performs the same task, but on different data.
  4533. Loosely coupled multi-threaded programs have threads that perform nearly
  4534. unrelated activities. Examples of these programs include communications file
  4535. servers, AI applications, simulations, and games (see the sidebar entitled
  4536. Loosely Coupled Multi-threaded Programs).
  4537. Multi-threaded programming is not a cure-all. It is something that every
  4538. programmer should have in her or his bag of tricks. Since multi-threaded
  4539. programming is portable, it is applicable without machine dependent
  4540. restrictions.
  4541. Like other programming techniques, multi-threaded programming presents a
  4542. learning curve. As the programming community becomes familiar with
  4543. multi-threading techniques, we will discover more ways to use it to simplify
  4544. our programming lives.
  4545. Loosely Coupled Multi-Threaded Programming
  4546. Many popular applications of multi-threaded programs are loosely coupled.
  4547. Loosely coupled programs consist of multiple unrelated threads of execution.
  4548. Loosely coupled multi-threaded applications are appealing because they allow
  4549. the programmer to create each thread independently as if it were a separate
  4550. program. After the programmer has coded and tested each thread, he/she easily
  4551. integrates the threads to create a sophisticated program.
  4552. Games are a common use of loosely coupled multi-threaded programs. Recently
  4553. Steve Halladay built a take-off program on the old space invaders game using
  4554. Multi-C. Those familiar with the game will remember that the game has armies,
  4555. space ships, bombs, bullets and guns. Each of these objects operates
  4556. independently of the other objects (unless a bomb or bullet contacts another
  4557. object).
  4558. Steve created the game by building each object independently. The first object
  4559. to be built was the space ship that flies across the top of the screen in
  4560. random directions. The space ship was thoroughly tested independently of the
  4561. other objects.
  4562. The next objects built were the gun and its bullets. The arrow keys and the
  4563. space bar control the gun (i.e., the arrows move the gun right and left, and
  4564. the space bar fires the gun). Loosely coupled multiple threads allowed the gun
  4565. to be built independently. Integrating the gun thread with the space ship
  4566. thread made it possible to test the ability to shoot down the space ship.
  4567. The loosely coupled threads allowed each additional object to be built,
  4568. developed, and tested independently. The next step integrated the independent
  4569. threads with the previously existing objects. The multi-threaded capabilities
  4570. simplified the integration process considerably. Most of the integration was
  4571. little more than linking in the new objects.
  4572. Once the game was complete, he wanted to create a demo that would play the
  4573. game by itself. Demo development consisted of developing a thread to simulate
  4574. keyboard activity and integrating the thread into the existing program. By
  4575. using an additional thread to perform this function, it was simple and yet
  4576. interesting to develop strategies for automating the game playing.
  4577. If you have further interest in loosely coupled multi-threaded programming or
  4578. this example program, you can contact Steve Halladay for a copy of its source
  4579. and executable. The program uses character graphics (via Aspen Scientific's
  4580. version of curses) and requires less than 100K bytes to run. It uses an
  4581. interrupt to continuously read the keyboard that may not be supported by some
  4582. older versions of BIOS. The program and its source can be freely copied, but
  4583. support is not available.
  4584. Figure 1
  4585. Table 1 Complexity Measures of Examples
  4586. Example N1 N2 N n1 n2 n V Lines of Code
  4587. ------------------------------------------------------
  4588. Single 86 99 185 24 31 55 1069 78
  4589. Multiple 33 28 61 15 13 28 293 23
  4590.  
  4591. Listing 1
  4592. /*
  4593. * Traversal of multiple binary trees
  4594. */
  4595.  
  4596. #include <stdio.h>
  4597. #include <stdlib.h>
  4598. #include <assert.h>
  4599.  
  4600. #define BIGINT (~(1 << ((8*sizeof(int))-1))) /* maximum integer */
  4601. #define NNODES 25 /* the number of nodes per tree */
  4602. #define NTREES 10 /* the number of binary trees to traverse */
  4603.  
  4604. /* binary tree node definition */
  4605. typedef struct node {
  4606. struct node *left; /* left sub-tree */
  4607. struct node *right; /* right sub-tree */
  4608. int key; /* current node value */
  4609. } NODE;
  4610.  
  4611. /***********************************************************************
  4612. * the following section is the single threaded example
  4613. ***********************************************************************/
  4614. #ifdef SINGLE_THREAD
  4615.  
  4616. /* stack frame definition */
  4617. typedef struct frame {
  4618. struct node *data; /* the node being stacked */
  4619. struct frame *next; /* the next stack entry */
  4620.  
  4621. } FRAME;
  4622.  
  4623. /* push a tree node onto the stack */
  4624. void Push(FRAME **stackp, NODE *treep) {
  4625. FRAME *temp;
  4626. assert(treep != NULL);
  4627. temp = malloc(sizeof(FRAME));
  4628. assert(temp != NULL);
  4629. temp->data = treep;
  4630. temp->next = *stackp;
  4631. (*stackp) = temp;
  4632. }
  4633.  
  4634. /* get the top data item from the stack (without popping) */
  4635. int Top(FRAME *stackp) {
  4636. NODE *tempt;
  4637.  
  4638. assert(stackp != NULL);
  4639. temp = stackp->data;
  4640. return(temp->key);
  4641. }
  4642.  
  4643. /* print the lowest value of all N trees */
  4644. void DisplayLowest(FRAME *stackp[], int *lowest) {
  4645. int current;
  4646. int topvalue;
  4647. int temp=BIGINT;
  4648. int i;
  4649.  
  4650. /* for each tree in the list... */
  4651. for (i=0; i < NTREES; i++) {
  4652. /* if the tree has not been completely traversed, */
  4653. if (stackp[i] != NULL) {
  4654. /* get the current key of this tree */
  4655. topvalue = Top(stackp[i]);
  4656. /* compare the current key with the other trees */
  4657. if (topvalue < temp) {
  4658. /* save the current tree number and its key */
  4659. current = i;
  4660. temp = topvalue;
  4661. }
  4662. }
  4663. }
  4664. printf("key = %d\n", temp);
  4665. *lowest = current;
  4666. }
  4667.  
  4668. /* removes and returns the top entry from the stack */
  4669. NODE *Pop(FRAME **stackp) {
  4670. FRAME *temp;
  4671. NODE *data;
  4672.  
  4673. temp = *stackp;
  4674. *stackp = (*stackp)->next;
  4675. data = temp->data;
  4676. free(temp);
  4677. return(data);
  4678. }
  4679.  
  4680.  
  4681. /* traverse the right sub-tree */
  4682. void TraverseRightSub(FRAME **stackp) {
  4683. NODE *temp;
  4684. FRAME *sframe;
  4685.  
  4686. /* find the right sub-tree of the current node */
  4687. temp = Pop(stackp);
  4688. temp = temp->right;
  4689.  
  4690. /* traverse the sub tree as far left as possible */
  4691. while (temp != NULL) {
  4692. Push(stackp, temp);
  4693. temp = temp->left;
  4694. }
  4695. }
  4696.  
  4697. /* determine if there are unvisited nodes */
  4698. int StacksExist(FRAME *stackp[]) {
  4699. int i;
  4700.  
  4701. for (i = 0; i < NTREES; i++){
  4702. if (stackp[i] != NULL)
  4703. return(1);
  4704. }
  4705. return(0);
  4706. }
  4707.  
  4708. void MergeTrees(NODE *treep[]) {
  4709. FRAME *stackp[NTREES];
  4710. int i;
  4711.  
  4712. /* preload all stacks */
  4713. for (i = 0; i < NTREES; i++) {
  4714. stackp[i] = NULL;
  4715. while (treep[i] != NULL) {
  4716. Push(&stackp[i], treep[i]);
  4717. treep[i] = treep[i]->left;
  4718. }
  4719. }
  4720. do {
  4721. DisplayLowest(stackp, &i);
  4722. TraverseRightSub(&stackp[i]);
  4723. } while(StacksExist(stackp));
  4724. }
  4725.  
  4726. /*****************************************************************
  4727.  
  4728. * the following section is the multi threaded example
  4729.  
  4730. *****************************************************************/
  4731.  
  4732. #else
  4733.  
  4734. #include <multi_c.h>
  4735.  
  4736. /* print the contents of a node (wait until it
  4737. is the lowest key) */
  4738. void PrintNode(NODE *tree) {
  4739. static int CurrentKey = BIGINT;
  4740.  
  4741.  
  4742. /* wait until this node contains the lowest key value */
  4743. do {
  4744. if (CurrentKey > tree->key)
  4745. CurrentKey = tree->key;
  4746. MtCYield();
  4747. } while (CurrentKey != tree->key);
  4748. /* reinitialize the current key */
  4749. CurrentKey = BIGINT;
  4750. printf("key = %d\n", tree->key);
  4751. }
  4752.  
  4753. /* recursively visit the nodes in a tree */
  4754. void PrintTree(NODE *tree) {
  4755.  
  4756. if (tree != NULL) {
  4757. PrintTree(tree->left);
  4758. PrintNode(tree);
  4759. PrintTree(tree->right);
  4760. }
  4761. }
  4762.  
  4763. /* traverse multiple binary trees */
  4764. void MergeTrees(NODE **trees) {
  4765. int i;
  4766.  
  4767. /* create a thread for each tree... */
  4768. for (i = 0; i < NTREES; i++) {
  4769. MtCCoroutine(PrintTree(&trees[i]));
  4770. }
  4771. }
  4772.  
  4773. #endif
  4774.  
  4775. /* recursively add a node to the tree */
  4776. void AddKey(NODE **treep, int key) {
  4777.  
  4778. if ((*treep) == NULL) {
  4779. (*treep) = malloc(sizeof(NODE));
  4780. assert(*treep != NULL);
  4781. (*treep)->left = NULL;
  4782. (*treep)->right = NULL;
  4783. (*treep)->key = key;
  4784. } else {
  4785. if (key < (*treep)->key) {
  4786. AddKey(&((*treep)->left), key);
  4787. } else {
  4788. AddKey(&((*treep)->right), key);
  4789. }
  4790. }
  4791. }
  4792.  
  4793. /* build a tree of NNODES random keys */
  4794. void BuildTree(NODE **treep) {
  4795. int i;
  4796.  
  4797. for (i = 0; i < NNODES; i++) {
  4798. AddKey(treep, rand());
  4799. }
  4800.  
  4801. }
  4802.  
  4803.  
  4804. void main() {
  4805. NODE *trees[NTREES];
  4806. int i;
  4807.  
  4808. /* randomly build the trees */
  4809. for (i = 0; i < NTREES; i++) {
  4810. trees[i] = NULL;
  4811. BuildTree(&trees[i]);
  4812. }
  4813. /* traverse the trees as a single tree */
  4814. MergeTrees(trees);
  4815. }
  4816.  
  4817. /* End of File */
  4818.  
  4819.  
  4820.  
  4821.  
  4822.  
  4823.  
  4824.  
  4825.  
  4826.  
  4827.  
  4828.  
  4829.  
  4830.  
  4831.  
  4832.  
  4833.  
  4834.  
  4835.  
  4836.  
  4837.  
  4838.  
  4839.  
  4840.  
  4841.  
  4842.  
  4843.  
  4844.  
  4845.  
  4846.  
  4847.  
  4848.  
  4849.  
  4850.  
  4851.  
  4852.  
  4853.  
  4854.  
  4855.  
  4856.  
  4857.  
  4858.  
  4859.  
  4860.  
  4861.  
  4862.  
  4863.  
  4864. A Console Stream Class For Borland C++
  4865.  
  4866.  
  4867. AI Williams
  4868.  
  4869.  
  4870. Al is the author of DOS 5: A Developer's Guide (M&T Press). He can be reached
  4871. at 310 Ivy Glen Ct., League City, TX 77573; or on Compuserve at 72010, 3574.
  4872.  
  4873.  
  4874. For my first programming job under Borland C++, I wanted to write a program
  4875. that used both stream I/O and text windows to handle I/O to and from the
  4876. screen. Most C++ programmers prefer using stream I/O instead of the
  4877. printf/scanf functions. To implement the text windows, I set out to develop a
  4878. simple C++ window manager. I planned to use Borland's window() function, but
  4879. soon found that the streams library did not support console I/O (analogous to
  4880. the functions in CONIO.H).
  4881.  
  4882.  
  4883. Stream I/O
  4884.  
  4885.  
  4886. Some examples of stream output in Borland C++ are:
  4887. cout << "Hello World\n";
  4888. cout << "Your age is " << ur_age;
  4889. Both examples use cout, a predefined output stream corresponding to stdout.
  4890. The first example sends the cliche string "Hello World\n" to cout. The second
  4891. example prints a string and a numeric variable (ur_age).
  4892. The << operator, while usually the shift left operator, has been overloaded to
  4893. return a stream or a stream reference. The operator used in this sense allows
  4894. running multiple I/O statements together, as in the second example. The
  4895. compiler interprets the statement as:
  4896. (cout << "Your age is ") << ur_age;
  4897. Since the first << operator returns a stream (cout, in fact), the statement is
  4898. equivalent to:
  4899. cout << "Your age is ";
  4900. cout << ur_age;
  4901. Stream input is similar:
  4902. cin >> ur_age;
  4903. cin >> name >> phone_number;
  4904. C++ predefines the cin stream to be the same as stdin. Unlike scanf(), stream
  4905. input doesn't require pointers to the input variables. In the previous
  4906. examples, ur_age is just an unsigned integer variable. The equivalent scanf()
  4907. calls are:
  4908. scanf ("%d" ,&ur_age);
  4909. scanf("%s %s",name,phone_number);
  4910. The C++ library defines input and output operators for the predefined types.
  4911. It is simple to add custom << operators to print user-defined types. Listing 1
  4912. shows a date class and an operator to print it on an output stream. Listing 1
  4913. also includes an overloaded >> operator to input a date. Of course, you can
  4914. use the same techniques to add stream I/O for structures and unions, too.
  4915. The ostream class that Listing 1 uses is the base class of all output streams.
  4916. The istream class is the base for input streams. These stream classes, which
  4917. are subclasses of the ios class, supply methods for formatted and unformatted
  4918. I/O.
  4919. Each stream has a buffer that sends and receives characters. For example, cout
  4920. uses a buffer that sends characters to the stdout device. These buffers are
  4921. either instances of the streambuf class, or instances of a subclass of
  4922. streambuf. The buffer classes handle all the problems of managing a buffer and
  4923. interfacing with the stream class. You are free to derive subclasses of the
  4924. streambuf class that talk to different devices.
  4925. Since the new class will inherit methods from streambuf, you don't have to
  4926. rewrite any of the buffer management methods that already exist. You must only
  4927. describe how the new stream differs from its base class. This technique is
  4928. known as "design by difference." Design by difference is part of what makes
  4929. object-oriented programming so attractive.
  4930. The C++ library also supplies subclasses of streambuf. The filebuf class
  4931. allows you to create buffers to access files. The strstreambuf class works
  4932. with streams that operate on strings, similar to C's sscanf() and sprintf()
  4933. functions.
  4934.  
  4935.  
  4936. Console Streams
  4937.  
  4938.  
  4939. VSTREAM.H and VSTREAM.CPP (Listing 2 and Listing 3) provide a console output
  4940. stream called conout for your programs. These files create a class, Conbuf,
  4941. that is a subclass of streambuf.
  4942. The only methods that must be defined for Conbuf are do_sputn()and overflow().
  4943. The methods that manage stream output don't change. The ostream class calls
  4944. do_sputn() to output one or more characters. The overflow() method handles
  4945. buffer overflow. Since the console stream does not require a buffer, the
  4946. overflow() method simply prints the offending character using do_sputn().
  4947. VSTREAM.CPP creates a single instance of the Conbuf class (conbuf), and stores
  4948. a pointer to it in the global variable conout. You can then use conout as cout
  4949. was used in the earlier examples.
  4950. Deriving Conbuf is a good example of the design by difference technique. You
  4951. don't have to worry about buffer management, formatting, and other
  4952. idiosyncrasies of stream output. You only have to describe the difference
  4953. between console output and the normal output streams.
  4954.  
  4955.  
  4956. The Window Manager
  4957.  
  4958.  
  4959. The window manager's design meets some simple goals:
  4960. Windows can be as large as the screen, but no larger.
  4961. Windows can have frames, or not.
  4962. When the manager removes a window, it restores any windows that become
  4963. visible.
  4964. Windows can be forced to the top of the window stack.
  4965. Only the top window can be accessed.
  4966. The window manager supports streams.
  4967. The region class, shown in Listing 4 and Listing 5, is the base class for
  4968. windows. This class simply saves and restores portions of the screen using
  4969. C++'s dynamic memory allocation.
  4970.  
  4971. The win class, built on top of the region class, provides all the basic window
  4972. functions. The manager defines windows with the Borland library function
  4973. window(). Therefore, all the output calls in CONIO.H and the new conout stream
  4974. will automatically write to the top window.
  4975. The boxwin class is another example of design by difference. Containing just
  4976. one function to draw a box, boxwin reuses all the code of its base class, win.
  4977. Listing 6 and Listing 7 contain the window class library. Listing 8 is a demo
  4978. program of the windows in action. Thanks to C++ constructors, you can simply
  4979. define a window in your program, and it appears on the screen. When your
  4980. program deallocates the window or it goes out of scope, the window vanishes
  4981. just as easily.
  4982.  
  4983.  
  4984. Inside The Window Class
  4985.  
  4986.  
  4987. To understand the window class, you must first start with the class named
  4988. region. The region class simply saves and restores a specified area of the
  4989. screen. The constructor for this class takes four arguments that specify the x
  4990. and y coordinates of the screen region to use. If the fifth argument, save, is
  4991. zero, the constructor will set up the region, but not save the screen area.
  4992. Otherwise, the region reads the contents of the screen (the default action).
  4993. Setting save to zero is useful for programs like the window class that require
  4994. finer control over the timing of region's actions.
  4995. The region class has three public methods besides the constructor and
  4996. destructor methods. The reinit method causes the region to read the screen,
  4997. discarding the previous contents of the region (if any). The restore method
  4998. causes the region to redisplay the saved area of the screen. This call also
  4999. discards the contents of the region. Finally, a call to discard will destroy
  5000. the region's contents without changing the screen.
  5001. The window class win maintains a stack of windows. The class variable topwin
  5002. points to the current top window. Notice that Listing 6 declares topwin as a
  5003. static member of the class. This declaration allows all instances of win to
  5004. share the same topwin variable. A similar variable, lastwin, points to the
  5005. last window in the stack. Pointers thread the stack together in both
  5006. directions. The pointers next (toward the bottom of the stack) and prev
  5007. (toward the top of the stack) contain links to adjacent windows. Of course,
  5008. these pointers are not static - each window has its own next and prev pointer.
  5009. The remaining data members of win are not static. They store the cursor
  5010. location, the window's color, a pointer to the next window, and a margin value
  5011. used for bordered windows.
  5012. The top window's region buffer remains empty. Only windows that are inactive
  5013. store their contents in the region. This is also true of the cursor location
  5014. values.
  5015. The win class has only one public method (excepting the constructor and
  5016. destructor). This method, maketop, forces the given window to the top of the
  5017. stack. If the window is already at the top, no action results.
  5018.  
  5019.  
  5020. More Fun With Windows
  5021.  
  5022.  
  5023. You will probably want to add functionality to the windows package. For
  5024. instance, you might want different box types, or to be able to move and resize
  5025. the windows. For serious use, you could add some simple methods to change
  5026. various private members, such as a setcolor method to change a window's color.
  5027. The built-in stream classes can take manipulators that control certain
  5028. formatting attributes. The standard headers IOSTREAM.H and IOMANIP.H declare
  5029. these manipulators. The following statement, for example, uses the hex
  5030. manipulator:
  5031. cout << hex << 16;
  5032. This statement prints 16 in hex (10) instead of decimal.
  5033. Adopting this syntax for managing the console stream would be quite natural.
  5034. You could write
  5035. conout << color_red << "RED" << color_white <<
  5036. " ALERT";
  5037. or, alternately:
  5038. conout << concolor(0x70) << "Text";
  5039. Other manipulators could duplicate functions found in CONIO.H. For example,
  5040. you could use a manipulator to set the cursor's location. Although this would
  5041. be an ambitious addition, it would make the stream class much more natural to
  5042. use.
  5043.  
  5044. Listing 1 (dates.cpp) Simple Date Class
  5045. #include <iostream.h>
  5046. #include <iomanip.h>
  5047.  
  5048. // Crude date class to demonstrate
  5049. // customized stream I/O -- no error checking attempted
  5050.  
  5051. class date
  5052. {
  5053. unsigned int year; // 0-99
  5054. unsigned int mon; // 1-12
  5055. unsigned int day; // 1-31
  5056. // Allow the I/O stream operators to access the private members
  5057. // of the date class. You can't define these as members
  5058. // because the first argument is a stream, not a date.
  5059. friend ostream & operator <<(ostream &s,date dt);
  5060. friend istream & operator >>(istream &s,date &dt);
  5061. public:
  5062. // constructor
  5063. date(int _mon=1,int _day=1,int _year=0)
  5064. { mon=_mon; day=_day; year=_year; }
  5065. };
  5066.  
  5067. // Output a date as: MM/DD/YY
  5068. // No attempt was made to pad the elements with zeros
  5069. ostream & operator <<(ostream &s,date dt)
  5070. {
  5071. s << dt.mon << "/" << dt.day << "/" << dt.year;
  5072. return s;
  5073. }
  5074.  
  5075.  
  5076. // Input a date in the format: MM/DD/YY -- allow any character
  5077. // to seperate the elements (i.e., MM-DD-YY, MM,DD,YY, etc.)
  5078. // Notice that the date is a reference (&dt) so we modify
  5079. // the actual date -- not a copy passed by value.
  5080. istream & operator >>(istream &s,date &dt)
  5081. {
  5082. int m,d,y;
  5083. char dummy; // this char holds the seperator
  5084. s >> m >> dummy >> d >> dummy >> y;
  5085. dt.mon=m;
  5086. dt.day=d;
  5087. dt.year=y;
  5088. return s;
  5089. }
  5090.  
  5091. // Simple demo for dates. Notice how the stream I/O operators
  5092. // have been overloaded to accept the date class.
  5093. main()
  5094. {
  5095. date jan1(1,1,70);
  5096. date bday;
  5097. cout << "Enter your birthday (MM/DD/YY): ";
  5098. cin >> bday;
  5099. cout << "The first date is " << jan1 << "\n";
  5100. cout << "Your birthday is " << bday << "\n";
  5101. cout << 1;
  5102. }
  5103.  
  5104. // End of File
  5105.  
  5106.  
  5107. Listing 2 (vstream.h) Video Stream Class
  5108. #ifndef _VSTREAMDEF
  5109. #define _VSTREAMDEF
  5110.  
  5111. #include <iostream.h>
  5112.  
  5113. extern class Conbuf : public streambuf
  5114. {
  5115. int do_sputn(const char *s,int n);
  5116. int overflow(int=EOF);
  5117. } conbuf;
  5118.  
  5119. extern ostream conout;
  5120.  
  5121. #endif
  5122.  
  5123. /* End of File */
  5124.  
  5125.  
  5126. Listing 3 (vstream.cpp) Video Stream Package
  5127. #include <iostream.h>
  5128. #include <conio.h>
  5129.  
  5130.  
  5131. class Conbuf : public streambuf
  5132. {
  5133. int do_sputn(const char *s,int n);
  5134.  
  5135. int overflow(int=EOF);
  5136. } conbuf;
  5137.  
  5138. int Conbuf::overflow(int c)
  5139. {
  5140. do_sputn((char *)&c,1);
  5141. return 1;
  5142. }
  5143.  
  5144. int Conbuf::do_sputn(const char *s,int n)
  5145. {
  5146. int n1=n;
  5147. while (n1--)
  5148. {
  5149. putch(*s);
  5150. if (*s++=='\n')
  5151. {
  5152. putch('\r');
  5153. clreol();
  5154. }
  5155. }
  5156. return n;
  5157. }
  5158.  
  5159. ostream conout=&conbuf;
  5160.  
  5161. // End of File
  5162.  
  5163.  
  5164. Listing 4 (region.h) Header for Screen Regions
  5165. #ifndef _REGIONDEF
  5166. #define _REGIONDEF
  5167. #include <stddef.h>
  5168. #include <conio.h>
  5169.  
  5170. // This class saves and releases a region of the screen
  5171. class region
  5172. {
  5173. protected:
  5174. // Screen coordinates
  5175. int left;
  5176. int top;
  5177. int right;
  5178. int bot;
  5179. // Storage area
  5180. char *buf;
  5181. public:
  5182. // Methods:
  5183.  
  5184. // Constructor -- if save is 0, the screen region isn't saved.
  5185. // You'd save it later with the reinit() method.
  5186. region(int x0,int y0,int x1,int y1,int save=1);
  5187.  
  5188. // Destructor
  5189. ~region();
  5190.  
  5191. // Force the region to reread its screen area and save it
  5192. void reinit(void);
  5193.  
  5194.  
  5195. // Restore screen data and destroy it
  5196. void restore(void);
  5197.  
  5198. // Destroy screen data with out restoring it
  5199. void destroy(void);
  5200. };
  5201.  
  5202. #endif
  5203.  
  5204. /* End of File */
  5205.  
  5206.  
  5207. Listing 5 (region.cpp) Screen Region Package
  5208. #include "region.h"
  5209. region::region(int x0, int y0, int x1, int y1, int save)
  5210. {
  5211. left=x0;
  5212. top=y0;
  5213. right=x1;
  5214. bot=y1;
  5215. buf=NULL;
  5216. if (save)
  5217. reinit();
  5218. }
  5219.  
  5220. void region::reinit(void)
  5221. {
  5222. if (buf) delete buf;
  5223. buf=new char[2*(1+right-left)*(1+bot-top)];
  5224. gettext(left,top,right,bot,buf);
  5225. }
  5226.  
  5227. void region::restore(void)
  5228. {
  5229. if (buf)
  5230. {
  5231. puttext(left,top,right,bot,buf);
  5232. destroy();
  5233. }
  5234. }
  5235.  
  5236.  
  5237. region::~region()
  5238. {
  5239. restore();
  5240. }
  5241.  
  5242. void region::destroy(void)
  5243. {
  5244. if (buf)
  5245. {
  5246. delete buf;
  5247. buf=NULL;
  5248. }
  5249. }
  5250.  
  5251. // End of File
  5252.  
  5253.  
  5254.  
  5255. Listing 6 (window.h)
  5256. #ifndef _WINCLASSDEF
  5257. #define _WINCLASSDEF
  5258.  
  5259. #include <stddef.h>
  5260. #include <conio.h>
  5261. #include "region.h"
  5262.  
  5263.  
  5264. // The basic window class
  5265. extern class win : public region
  5266. {
  5267. protected:
  5268.  
  5269. static win *topwin; // Class variable holds top window
  5270. static win *lastwin; // Last window
  5271.  
  5272. // Cursor location when window isn't on top
  5273. int oldx;
  5274. int oldy;
  5275.  
  5276. // Default screen color
  5277. unsigned int color;
  5278.  
  5279. // Pointer to next window on stack
  5280. win *next; // Pointer to next window
  5281. win *prev; // Previous window
  5282.  
  5283. int margin; // Margins support borders on the windows
  5284.  
  5285. // Private method to register top window
  5286. void settop(void);
  5287.  
  5288. public:
  5289. // Methods:
  5290. // Constructor:
  5291. win(int x0=1,int y0=1,int x1=80,int y1=25,
  5292. unsigned int clr=7,int mar=0);
  5293.  
  5294. // Destructor. This is virtual to support boxwindows, etc.
  5295. virtual ~win();
  5296.  
  5297. // Force window to top of stack
  5298. void maketop();
  5299. };
  5300.  
  5301. // Windows with borders
  5302. extern class boxwin : public win
  5303. {
  5304. public:
  5305. boxwin(int x0=2,int y0=2,int x1=79,int y1=24,
  5306. unsigned int clr=7,int boxt=0);
  5307. };
  5308.  
  5309. // General purpose box drawing routine
  5310. void draw_box(int type,int x0,int y0,int x1,int y1);
  5311.  
  5312. #endif
  5313.  
  5314.  
  5315. /* End of File */
  5316.  
  5317.  
  5318. Listing 7 (window. cpp)
  5319. #include "window.h"
  5320. #include "vstream.h" // console stream header
  5321.  
  5322. // TC++ 1.0 didn't define _wscroll in conio.h
  5323. #ifndef__BORLANDC______LINEEND____
  5324. extern int_wscroll;
  5325. #endif
  5326.  
  5327. // Initialize class variable. Note you can't do this in the
  5328. // Definition itself.
  5329. win * win::topwin=NULL;
  5330. win * win::lastwin=NULL;
  5331.  
  5332. win::win(int x0,int y0,int x1,int y1,unsigned int clr,int mar):
  5333. region(x0,y0,x1,y1,0)
  5334. {
  5335. if (!topwin) // first window
  5336. {
  5337. textattr(7); // reset screen
  5338. clrscr();
  5339. lastwin=this;
  5340. }
  5341. else
  5342. {
  5343. // save window contents & cursor
  5344. topwin->reinit();
  5345. topwin->oldx=wherex();
  5346. topwin->oldy=wherey();
  5347. }
  5348. margin=mar;
  5349. color=clr;
  5350. prev=NULL;
  5351. if (topwin) topwin->prev=this;
  5352. next=topwin;
  5353. topwin=this;
  5354. window(x0,y0,x1,y1);
  5355. gotoxy(1,1);
  5356. textattr(clr);
  5357. clrscr();
  5358. }
  5359.  
  5360. void win::maketop(void)
  5361. {
  5362. win *gpw;
  5363. // return if already at top
  5364. if (this==topwin) return;
  5365. // force top window to save
  5366. topwin->reinit();
  5367. topwin->oldx=wherex();
  5368. topwin->oldy=wherey();
  5369. // patch link list
  5370. if (lastwin==this) lastwin=prev;
  5371. if (prev) prev->next=next;
  5372. if (next) next->prev=prev;
  5373. prev=NULL;
  5374.  
  5375. topwin->prev=this;
  5376. next=topwin;
  5377. topwin=this;
  5378. settop();
  5379. restore(); // Draw our screen contents
  5380. }
  5381.  
  5382. void win::settop(void)
  5383. {
  5384. window(
  5385. topwin->left+topwin->margin,
  5386. topwin->top+topwin->margin,
  5387. topwin->right-topwin->margin,
  5388. topwin->bot-topwin->margin);
  5389. textattr(topwin->color);
  5390. gotoxy(topwin->oldx,topwin->oldy);
  5391. }
  5392.  
  5393.  
  5394.  
  5395. win::~win()
  5396. {
  5397. this->maketop(); // force us on top
  5398. // just in case there is a margin
  5399. window(left,top,right,bot);
  5400. textattr(7);
  5401. clrscr();
  5402. destroy();
  5403.  
  5404. if (next) next->prev=NULL;
  5405. topwin=next;
  5406. if (!topwin)
  5407. {
  5408. window(1,1,80,25);
  5409. clrscr();
  5410. }
  5411. else
  5412. {
  5413. for (win *i=lastwin;i;i=i->prev)
  5414. {
  5415. i->restore();
  5416. if (i!=topwin) i->reinit();
  5417. }
  5418. settop();
  5419. }
  5420. }
  5421.  
  5422. // boxwin methods
  5423. boxwin::boxwin(int x0,int y0,int x1,int y1,unsigned int clr, int boxt) :
  5424. win(x0-1,y0-1,x1+1,y1+1,clr,1)
  5425. {
  5426. draw_box(boxt,1,1,x1-x0+3,y1-y0+3);
  5427. window(x0,y0,x1,y1);
  5428. }
  5429.  
  5430. // General purpose box drawing function
  5431. // Type 0: single line box
  5432. // Type 1: double line box
  5433. // Other types are easily added
  5434.  
  5435. void draw_box(int type,int x0,int y0,int x1,int y1)
  5436. {
  5437. int oldscroll; // old value for _wscroll
  5438. int i;
  5439. int hline;
  5440. int vline;
  5441. int cl,c2,c3,c4;
  5442. int xlen;
  5443. int ylen;
  5444. if (type<0type>1) return; // change value to add more types
  5445. xlen=x1-x0;
  5446. ylen=y1-y0;
  5447. if (type==0)
  5448. {
  5449.  
  5450. // Constants for a "normal" box
  5451. hline=196;
  5452. vline=179;
  5453. c1=218;
  5454. c2=191;
  5455. c3=192;
  5456. c4=217;
  5457. }
  5458. else if (type==1)
  5459. {
  5460. hline=205;
  5461. vline=186;
  5462. c1=201;
  5463. c2=187;
  5464. c3=200;
  5465. c4=188;
  5466. }
  5467. oldscroll= _wscroll;
  5468. _wscroll=0;
  5469. gotoxy(x0+1,y0);
  5470. for (i=1;i<xlen;i++) putch(hline);
  5471. gotoxy(x0+1,y0+ylen);
  5472. for (i=1;i<xlen;i++) putch(hline);
  5473. gotoxy(x0,y0);
  5474. putch(cl);
  5475. gotoxy(x0+xlen,y0);
  5476. putch(c2);
  5477. gotoxy(x0,ylen+y0);
  5478. putch(c3);
  5479. gotoxy(xlen+x0,ylen+y0);
  5480. putch(c4);
  5481. for (i=y0;i<ylen;i++)
  5482. {
  5483. gotoxy(x0,i+1);
  5484. putch(vline);
  5485. gotoxy(xlen+x0,i+1);
  5486. putch(vline);
  5487. }
  5488. _wscroll=oldscroll;
  5489. }
  5490.  
  5491. // End of File
  5492.  
  5493.  
  5494.  
  5495. Listing 8 (wintest.cpp)
  5496. #include "window.h"
  5497. #include <ctype.h>
  5498. #include "vstream.h"
  5499.  
  5500. // Make sure user really wants to quit
  5501. int cfmexit(void)
  5502. {
  5503. int c;
  5504. boxwin promptwin(30,12,50,14,0x70,1);
  5505. conout<<"Really quit? (Y/N)";
  5506. while (1)
  5507. {
  5508. c:getche();
  5509. if (!c) getch(); // ignore Function keys
  5510. c:toupper(c);
  5511. if (c=='Y') return 1;
  5512. if (c=='N') return 0;
  5513. }
  5514. }
  5515.  
  5516.  
  5517. // Main routine
  5518. main()
  5519. (
  5520. /* make main window */
  5521. boxwin mainwindow(2,20,78,23,0x70);
  5522. win *w[4];
  5523. conout<<"Welcome to the WINDOWS++ demo.\n";
  5524. conout<<"Initializing windows...\n";
  5525. w[3]=new boxwin(60,2,78,10,0x70);
  5526. conout<<"Window #4";
  5527. w[2]=new boxwin(40,2,70,10,0x3F);
  5528. conout<<"Window #3";
  5529. w[1]=new boxwin(20,2,50,10,0x17);
  5530. conout<<"Window #2";
  5531. w[0]=new boxwin (2,2,30,10,7);
  5532. conout<<"Window #1";
  5533. mainwindow.maketop();
  5534. while (1)
  5535. {
  5536. int c;
  5537. conout<<
  5538. "Press 1-4 to select window or <Esc> to quit\n";
  5539. c=getch();
  5540. if (c==27)
  5541. if (cfmexit()) break; else continue;
  5542. if (c<'l' c>'4')
  5543. {
  5544. conout<<"Unknown window!\n";
  5545. continue;
  5546. }
  5547. conout<<"Activating window "<<(char)c<<'\n';
  5548. w[c-'l']->maketop();
  5549. mainwindow.maketop();
  5550. }
  5551. for (int i:0=i<4;i++) delete w[i];
  5552. }
  5553.  
  5554.  
  5555. // End of File
  5556.  
  5557.  
  5558.  
  5559.  
  5560.  
  5561.  
  5562.  
  5563.  
  5564.  
  5565.  
  5566.  
  5567.  
  5568.  
  5569.  
  5570.  
  5571.  
  5572.  
  5573.  
  5574.  
  5575.  
  5576.  
  5577.  
  5578.  
  5579.  
  5580.  
  5581.  
  5582.  
  5583.  
  5584.  
  5585.  
  5586.  
  5587.  
  5588.  
  5589.  
  5590.  
  5591.  
  5592.  
  5593.  
  5594.  
  5595.  
  5596.  
  5597.  
  5598.  
  5599.  
  5600.  
  5601.  
  5602.  
  5603.  
  5604.  
  5605.  
  5606.  
  5607.  
  5608.  
  5609.  
  5610.  
  5611.  
  5612.  
  5613.  
  5614.  
  5615.  
  5616.  
  5617.  
  5618. Standard C
  5619.  
  5620.  
  5621. Implementing <stdio.h>
  5622.  
  5623.  
  5624.  
  5625.  
  5626. P.J. Plauger
  5627.  
  5628.  
  5629. P.J. Plauger is senior editor of The C Users Journal. He is secretary of the
  5630. ANSI C standards committee, X3J11, and convenor of the ISO C standards
  5631. committee, WG14. His latest book is The Standard C Library, published by
  5632. Prentice-Hall. You can reach him at PJP@wsa.oz; or uunet!munnari!wsa.oz!pjp.
  5633.  
  5634.  
  5635.  
  5636.  
  5637. Introduction
  5638.  
  5639.  
  5640. The header <stdio.h> is far and away the largest one in the Standard C
  5641. library. Only <stdlib.h> comes close in size, and that one is a collection of
  5642. several unrelated groups of functions. By contrast, the header <stdio.h>
  5643. focuses exclusively on one topic -- performing input and output.
  5644. I have discussed many aspects of this header in the past:
  5645. "Evolution of the C I/O Model," CUJ August, 1989.
  5646. "Streams," CUJ September/ October, 1989.
  5647. "Formatted Output," CUJ November, 1989.
  5648. "Formatted Input," CUJ December/January, 1990.
  5649. If you don't have access to back issues of CUJ, take heart. You will find most
  5650. of these words recycled in Chapter 12: <stdio.h> of The Standard C Library. I
  5651. am not about to repeat them yet again here.
  5652. I am also not going to follow my usual practice of quoting the relevant
  5653. portion of the C Standard. That would take a whole column in its own right. I
  5654. have no qualms about getting paid in part for quoting from the standard -- I
  5655. feel I contributed significantly to developing those words. I just don't
  5656. believe that such an extensive quote best serves the goal of this column -- to
  5657. broaden your understanding of Standard C.
  5658. Instead I will go right to the really new stuff. I describe how I implemented
  5659. the functions in <stdio.h.> The challenges are:
  5660. to keep as much code as possible portable
  5661. to ensure that the system-dependent code is implementable efficiently on many
  5662. systems
  5663. to keep performance up
  5664. to keep the code as simple and readable as possible despite the above
  5665. requirements
  5666. Remember, what I show here is just one possible implementation. Different
  5667. approaches can be better, depending on circumstances. My purpose in showing a
  5668. particular implementation is to illustrate how <stdio.h> can work, not how it
  5669. must work.
  5670. Two design decisions are critical to the implementation of <stdio.h>:
  5671. the contents of the FILE data structure
  5672. the low-level primitives that interact with the operating system to perform
  5673. the actual input/output
  5674. I begin by discussing the first of these two topics in detail. You can then
  5675. appreciate how the portable low-level I/O functions work. I save the
  5676. primitives for later.
  5677.  
  5678.  
  5679. Data Structures
  5680.  
  5681.  
  5682. Listing 1 shows the file stdio.h. By now you should be familiar with my use of
  5683. the internal header <yvals.h> to supply implementation-dependent parameters.
  5684. Here are the parameters defined in <yvals.h> that affect <stdio.h>, with some
  5685. reasonable values for them:
  5686. #define _NULL (void *)0/* value for NULL */
  5687. #define _FNAMAX 64 /* value for FILENAME_MAX */
  5688. #define _FOPMAX 32 /* value for FOPEN_MAX */
  5689. #define _TNAMAX 16 /* value for TMP_MAX */
  5690. The file stdio.h contains a few other mysteries which shall become clear in
  5691. time. For now, I concentrate on the type definition FILE. Its members are:
  5692. _Mode -- a set of status bits for the stream, defined below
  5693. _Handle -- the handle, or file descriptor, returned by the operating system
  5694. for the opened file
  5695. _Buf -- a pointer to the start of the stream buffer, or a null pointer if no
  5696. buffer has been allocated
  5697. _Bend -- a pointer to the first character beyond the end of the buffer,
  5698. undefined if_Buf is a null pointer
  5699. _Next -- a pointer to the next character to read or write, never a null
  5700. pointer
  5701. _Rend -- a pointer to the first character beyond the end of data to be read,
  5702. never a null pointer
  5703. _Rsave -- holds_Rend if characters have been pushed back
  5704. _Wend -- a pointer to the first character beyond the end of where data can be
  5705. written, never a null pointer
  5706. _Back -- a stack of pushed-back characters
  5707.  
  5708. _Cbuf -- a one-character buffer to use when no other buffer is available
  5709. _Nback -- a count of the number of pushed-back characters
  5710. _Tmpnam -- a pointer to the name of a temporary file to be removed when the
  5711. file is closed, or a null pointer
  5712. The design of the FILE data structure is driven by the needs of the macros
  5713. getc and putc (and their companions getchar and putchar). Each of these
  5714. expands to a conditional expression that either accesses the stream buffer
  5715. directly or calls the underlying function. The predicate (test expression)
  5716. part of the conditional expression must be simple and always safe to execute.
  5717. Thus, str->_Next < str->_Rend is always true if characters that can be read
  5718. are in the buffer for the stream pointed at by str. And str->_Next <
  5719. str->_Wend is always true if space is available in the buffer to write
  5720. characters to the stream. An expression such as str->_Wend = str->_Buf, for
  5721. example, disallows writes to the buffer from these macros.
  5722. The functions that you call to read and write streams make more extensive
  5723. tests. A read function, for example, distinguishes a variety of conditions
  5724. such as: characters are available, buffer currently exhausted, end-of-file
  5725. encountered, buffer not yet allocated, reading currently disallowed, and
  5726. reading never allowed. The functions rely heavily on the various indicators in
  5727. the member _Mode to make those distinctions.
  5728. Only functions within the Standard C library need be privy to the meaning of
  5729. these indicators. For that reason, and others, I created the internal header
  5730. "xstdio.h". All the functions described in this chapter include "xstdio.h". It
  5731. defines macros for the stream-mode indicators. It includes <stdio.h> and
  5732. declares all the internal functions used to implement the capabilities of
  5733. <stdio.h>. It also defines a number of macros and types of interest only to
  5734. the formatted input and output functions.
  5735. Unlike <stdio.h>, the header "xstdio.h" contains too many distractions to
  5736. present at this point. I show you what goes into it only as the need arises.
  5737. Here, for example, are the macro names for the various indicators in the
  5738. member _Mode. Each is defined as a value with a different bit set, as in 0x1,
  5739. 0x2, 0x4, 0x8, and so on. The actual values are unimportant, so I omit them
  5740. here:
  5741. _MOPENR -- set if file is open for reading
  5742. _MOPENW -- set if file is open for writing
  5743. _MOPENA -- set if all writes append to end of file
  5744. _MTRUNC -- set if existing file was truncated on open (not used after open)
  5745. _MCREAT -- set if a new file can be created on open (not used after open)
  5746. _MBIN -- set if stream is binary, not set if stream is interpreted as text
  5747. _MALBUF -- set if the buffer must be freed on close
  5748. _MALFIL -- set if the FILE data object must be freed on close
  5749. _MEOF -- the end-of-file indicator
  5750. _MERR -- the error indicator
  5751. _MLBF -- set if line buffering is in effect
  5752. _MNBF -- set if no buffering should occur
  5753. _MREAD -- set if a read has occurred since last file-positioning operation
  5754. _MWRITE -- set if a write has occurred since last file-positioning operation
  5755. These macros have private names -- beginning with an underscore and an
  5756. uppercase letter -- even though they don't have to. As I developed the
  5757. library, I found myself moving them in and out of <stdio.h>. Some versions of
  5758. the macros visible to user programs used these macro names, later versions did
  5759. not. In the end, I left the names in this form as insurance. You may find
  5760. occasion to introduce macros that manipulate the indicators in the member
  5761. _Mode.
  5762. The indicators are actually the union of two sets. One is the set of
  5763. indicators that determines how to open a file. The other is the set of
  5764. indicators that helps record the state of the stream. Since the two sets
  5765. partially overlap, I chose to keep them all in one "space" of bit encodings. A
  5766. tidier implementation might well choose to separate the two uses. You might
  5767. also want to define two sets of values if you are starved for bits in _Mode.
  5768. In either case, you must add code to translate between the two
  5769. representations.
  5770.  
  5771.  
  5772. Opening And Closing Files
  5773.  
  5774.  
  5775. The best way to see how the library uses a FILE data object is to track one
  5776. through its lifetime. Listing 2 shows the file fopen.c. It defines the
  5777. function fopen that you call to open a file by name. That function first looks
  5778. for an idle entry in the static array of FILE pointers called _Files. It
  5779. contains FOPEN_MAX elements. If all of these point to FILE data objects for
  5780. open files, all subsequent open requests fail.
  5781. Listing 3 shows the file xfiles.c that defines the _Files data object. It
  5782. defines static instances of FILE data objects for the three standard streams.
  5783. Each is initialized to be open with appropriate parameters. I have wired in
  5784. the handles 0 for standard input, 1 for standard output, and 2 for standard
  5785. error. This is a widely used convention, inherited from UNIX. You may have to
  5786. alter or map these values.
  5787. Elements beyond the first three in _Files are initialized to null pointers.
  5788. Should fopen discover one of these, the function allocates a FILE data object
  5789. and marks it to be freed on close. fopen discovers a closed standard stream by
  5790. observing a non-null element of _Files that points at a FILE data object whose
  5791. member _Mode is zero.
  5792. fopen calls on the internal function _Foprep to complete the process of
  5793. opening a file. Listing 4 shows the file freopen.c. The function freopen also
  5794. calls this internal function. Note how it records the state of the indicator
  5795. _MALFIL until after fclose has closed the file currently associated with the
  5796. stream. The one operation that freopen does not want fclose to perform is to
  5797. free the FILE data object.
  5798. You may as well see fclose too, at this point. Listing 5 shows the file
  5799. fclose.c. It undoes the work of the file-opening functions in a fairly obvious
  5800. fashion. The one bit of magic is where it calls the function _Fclose to close
  5801. the file associated with the stream.
  5802. Listing 6 shows the file xfoprep.c that defines the function _Foprep. It
  5803. parses the mods (second) argument to fopen or freopen, at least as much as it
  5804. can understand, and initializes members of the FILE data object accordingly.
  5805. In the end, however, it must call on some outside agency to finish the job of
  5806. opening the file. _Foprep passes on the file name, the encoded indicators, and
  5807. whatever is left of mods to a function called _Fopen.
  5808.  
  5809.  
  5810. Primitives
  5811.  
  5812.  
  5813. _Fclose and _Fopen are two of several low-level primitives that stand between
  5814. <stdio.h> and the outside world. Each must perform a standardized function for
  5815. the Standard C library. Each must also be reasonably easy to tailor for the
  5816. divergent needs of different operating systems. This implementation has nine
  5817. functions in <stdio.h> that must be tailored to each operating system.
  5818. By implementing these interface primitives, you can use this library in
  5819. conjunction with several popular operating systems. I have cobbled up versions
  5820. that work with:
  5821. Turbo C on PC compatibles
  5822. Sun UNIX on Sun 3 workstations
  5823. ULTRIX on DEC VAX minicomputers
  5824. I say "cobbled" because my versions cut an occasional corner. They may, for
  5825. example, call functions that violate the name-space caveats of the C Standard.
  5826. (I may call unlink instead of writing an assembly-language equivalent called
  5827. _Unlink.) Or they may not deal with all the nonstandard ways that a carriage
  5828. return can appear within an MS-DOS file.
  5829. Nevertheless, I'm comfortable that these primitives are reasonable and
  5830. workable. Next month, I'll discuss the I/O primitives in detail. I'll also
  5831. show you an example of one way to float <stdio.h> atop an operating system.
  5832. This article is excerpted in part from P.J. Plauger, The Standard C Library,
  5833. (Englewood Cliffs, N.J.: Prentice-Hall, 1992).
  5834.  
  5835. Listing 1 (stdio.h)
  5836. /* stdio.h standard header */
  5837. #ifndef _STDIO
  5838. #define _STDIO
  5839. #ifndef _YVALS
  5840. #include <yvals.h>
  5841. #endif
  5842. /* macros */
  5843. #define NULL _NULL
  5844. #define _IOFBF 0
  5845. #define _IOLBF 1
  5846.  
  5847. #define _IONBF 2
  5848. #define BUFSIZ 512
  5849. #define EOF -1
  5850. #define FILENAME_MAX _FNAMAX
  5851. #define FOPEN_MAX _FOPMAX
  5852. #define L_tmpnam _TNAMAX
  5853. #define TMP_MAX 32
  5854. #define SEEK_SET 0
  5855. #define SEEK_CUR 1
  5856. #define SEEK_END 2
  5857. #define stdin _Files[0]
  5858. #define stdout _Files[1]
  5859. #define stderr _Files[2]
  5860. /* type definitions */
  5861. #ifndef _SIZET
  5862. #define _SIZET
  5863. typedef _Sizet size_t;
  5864. #endif
  5865. typedef struct {
  5866. unsigned long _Off; /* system dependent */
  5867. } fpos_t;
  5868. typedef struct {
  5869. unsigned short _Mode;
  5870. short _Handle;
  5871. unsigned char *_Buf, *_Bend, *_Next;
  5872. unsigned char *_Rend, *_Rsave, *_Wend;
  5873. unsigned char _Back[2],_Cbuf, _Nback;
  5874. char *_Tmpnam;
  5875. } FILE;
  5876. /* declarations */
  5877. void clearerr(FILE *);
  5878. int fclose(FILE *);
  5879. int feof(FILE *);
  5880. int ferror(FILE *);
  5881. int fflush(FILE *);
  5882. int fgetc(FILE *);
  5883. int fgetpos(FILE *, fpos_t *);
  5884. char *fgets(char *, int, FILE *);
  5885. FILE *fopen(const char *, const char *);
  5886. int fprintf(FILE *, const char *, ...);
  5887. int fputc(int, FILE *);
  5888. int fputs(const char *, FILE *);
  5889. size_t fread(void *, size_t, size_t, FILE *);
  5890. FILE *freopen(const char *, const char *, FILE *);
  5891. int fscanf(FILE *, const char *, ...);
  5892. int fseek(FILE *, long, int);
  5893. int fsetpos(FILE *, const fpos_t *);
  5894. long ftell(FILE *);
  5895. size_t fwrite(const void *, size_t, size_t, FILE *);
  5896. int getc(FILE *);
  5897. int getchar(void);
  5898. char *gets(char *);
  5899. void perror(const char *);
  5900. int printf(const char *, ...);
  5901. int putc(int, FILE *);
  5902. int putchar(int);
  5903. int puts(const char *);
  5904. int remove(const char *);
  5905. int rename(const char *, const char *);
  5906.  
  5907. void rewind(FILE *);
  5908. int scanf(const char *, ...);
  5909. void setbuf(FILE *, char *);
  5910. int setvbuf(FILE *, char *, int, size_t);
  5911. int sprintf(char *, const char *, ...);
  5912. int sscanf(const char *, const char *, ...);
  5913. FILE *tmpfile(void);
  5914. char *tmpnam(char *);
  5915. int ungetc(int, FILE *);
  5916. int vfprintf(FILE *, const char *, char *);
  5917. int vprintf(const char *, char *);
  5918. int vsprintf(char *, const char *, char *);
  5919. long _Fgpos(FILE *, fpos_t *);
  5920. int_Fspos(FILE *, const fpos_t *, long, int);
  5921. extern FILE *_Files[FOPEN_MAX];
  5922. /* macro overrides */
  5923. #define fgetpos(str, ptr) (int)_Fgpos(str, ptr)
  5924. #define fseek(str, off, way) _Fspos(str, _NULL, off, way)
  5925. #define fsetpos(str, ptr) _Fspos(str, ptr, 0L, 0)
  5926. #define ftell(str) _Fgpos(str, _NULL)
  5927. #define getc(str) ((str)->_Next < (str)->_Rend \
  5928. ? *(str)->_Next++ : (getc)(str))
  5929. #define getchar() \
  5930. (_Files[0]->_Next < _Files[0]->_Rend \
  5931. ? *_Files[0]->_Next++ : (getchar)())
  5932. #define putc(c, str) \
  5933. ((str)->_Next < (str)->_Wend \
  5934. ? (*(str)->_Next++ = c) : (putc)(c, str))
  5935. #define putchar(c) \
  5936. (_Files[1]->_Next <_Files[1]->_Wend \
  5937. ? (*_Files[1]->_Next++ = c) : (putchar)(c))
  5938. #endif
  5939. /* End of File */
  5940.  
  5941.  
  5942. Listing 2 (fopen.c)
  5943. * fopen function */
  5944. #include <stdlib.h>
  5945. #include "xstdio.h"
  5946.  
  5947. FILE *(fopen)(const char *name, const char *mods)
  5948. { /* open a file */
  5949. FILE *str;
  5950. size_t i;
  5951.  
  5952. for (i = 0; i < FOPEN_MAX; ++i)
  5953. if (_Files[i] == NULL)
  5954. { /* setup empty _Files[i] */
  5955. str = malloc(sizeof (FILE));
  5956. if (str == NULL)
  5957. return (NULL);
  5958. _Files[i] = str;
  5959. str->_Mode = _MALFIL;
  5960. break;
  5961. }
  5962. else if (_Files[i]-> _Mode == 0)
  5963. { /* setup preallocated
  5964. _Files[i] */
  5965. str = _Files[i];
  5966.  
  5967. break;
  5968. }
  5969. if (FOPEN_MAX <= i)
  5970. return (NULL);
  5971. return (_Foprep(name, mods, str));
  5972. }
  5973. /* End of File */
  5974.  
  5975.  
  5976. Listing 3 (xfiles.c)
  5977. /* _Files data object */
  5978. #include "xstdio.h"
  5979.  
  5980. /* standard error buffer */
  5981. static unsigned char ebuf[80];
  5982.  
  5983. /* the standard streams */
  5984. static FILE sin = { /* standard input */
  5985. _MOPENR, 0,
  5986. NULL, NULL, &sin._Cbuf,
  5987. &sin._Cbuf, NULL, &sin._Cbuf, };
  5988. static FILE sout = { /* standard output */
  5989. _MOPENW, 1,
  5990. NULL, NULL, &sout._Cbuf,
  5991. &sout._Cbuf, NULL, &sout._Cbuf, };
  5992. static FILE serr = { /* standard error */
  5993. _MOPENW_MNBF, 2,
  5994. ebuf, ebuf + sizeof (ebuf), ebuf,
  5995. ebuf, NULL, ebuf, };
  5996.  
  5997. /* the array of stream pointers */
  5998. FILE *_Files[FOPEN_MAX] = {&sin, &sout, &serr};
  5999. /* End of File */
  6000.  
  6001.  
  6002. Listing 4 (freopen.c)
  6003. /* freopen function */
  6004. #include <stdlib.h>
  6005. #include "xstdio.h"
  6006.  
  6007. FILE *(freopen)(const char *name, const char *mods, FILE *str)
  6008. { /* reopen a file */
  6009. unsigned short mode = str->_Mode & _MALFIL;
  6010.  
  6011. str->_Mode &=~_MALFIL;
  6012. fclose(str);
  6013. str-> _Mode = mode;
  6014. return (_Foprep(name, mods, str));
  6015. }
  6016. /* End of File */
  6017.  
  6018.  
  6019. Listing 5 (fclose.c)
  6020. /* fclose function */
  6021. #include <stdlib.h>
  6022. #include "xstdio.h"
  6023. #include "yfuns.h"
  6024.  
  6025. int (fclose)(FILE *str)
  6026.  
  6027. { /* close a stream */
  6028. int stat = fflush(str);
  6029.  
  6030. if (str-> _Mode & _ MALBUF)
  6031. free(str->_Buf);
  6032. str->_Buf = NULL;
  6033. if (0<= str->_Handle && _Fclose(str))
  6034. stat = EOF;
  6035. if (str->_Tmpnam)
  6036. { /* remove temp file */
  6037. if (remove(str->_Tmpnam))
  6038. stat = EOF;
  6039. free(str->_Tmpnam);
  6040. str->_Tmpnam = NULL;
  6041. }
  6042. str->_Mode = 0;
  6043. str->_Next = &str->_Cbuf;
  6044. str->_Rend = &str->_Cbuf;
  6045. str->_Wend = &str->_Cbuf;
  6046. str->_Nback = 0;
  6047. if (str->_Mode & _MALFIL)
  6048. { /* find _Files[i] entry and free */
  6049. size _t i;
  6050.  
  6051. for (i = 0; i < FOPEN_MAX; ++i)
  6052. if (_Files[i] == str)
  6053. { /* found entry */
  6054. _Files[i] = NULL;
  6055. break;
  6056. }
  6057. free(str);
  6058. }
  6059. return (stat);
  6060. }
  6061. /* End of File /*
  6062.  
  6063.  
  6064. Listing 6 (xfoprep.c)
  6065. /* _Foprep function */
  6066. #include "xstdio.h"
  6067.  
  6068. /* open a stream */
  6069. FILE *_Foprep(const char *name, const char *mods,
  6070. FILE *str)
  6071. { /* make str safe for fclose, macros */
  6072. str->_Handle = -1;
  6073. str->_Tmpnam = NULL;
  6074. str->_Buf = NULL;
  6075. str->_Next = &str->_Cbuf;
  6076. str->_Rend = &str->_Cbuf;
  6077. str->_Wend = &str->_Cbuf;
  6078. str->_Nback = 0;
  6079. str->_Mode = (str->_Mode & _MALFIL)
  6080.  (*mods == 'r' ? _MOPENR
  6081. : *mods == 'w' ? _MCREAT_MOPENW_MTRUNC
  6082. : *mods == 'a' ? _MCREAT_MOPENW_MOPENA
  6083. : 0);
  6084. if ((str->_Mode & (_MOPENR_MOPENW)) == 0)
  6085. { /* bad mods */
  6086.  
  6087. fclose(str);
  6088. return (NULL);
  6089. }
  6090. while (*++mods== 'b' *mods == '+')
  6091. if (*mods == 'b')
  6092. if (str->_Mode & _MBIN)
  6093. break;
  6094. else
  6095. str->_Mode = _MBIN;
  6096. else
  6097. if ((str->_Mode &
  6098. (_MOPENR_MOPENW))
  6099. == (_MOPENR_MOPENW))
  6100. break;
  6101. else
  6102. str->_Mode =
  6103. _MOPENR_MOPENW;
  6104. str->_Handle = _Fopen(name, str->_Mode, mods);
  6105. if (str->_Handle < 0)
  6106. { /* open failed */
  6107. fclose(str);
  6108. return (NULL);
  6109. }
  6110. return (str);
  6111. }
  6112.  
  6113. /* End of File /*
  6114.  
  6115.  
  6116.  
  6117.  
  6118.  
  6119.  
  6120.  
  6121.  
  6122.  
  6123.  
  6124.  
  6125.  
  6126.  
  6127.  
  6128.  
  6129.  
  6130.  
  6131.  
  6132.  
  6133.  
  6134.  
  6135.  
  6136.  
  6137.  
  6138.  
  6139.  
  6140.  
  6141.  
  6142.  
  6143.  
  6144.  
  6145.  
  6146.  
  6147.  
  6148.  
  6149.  
  6150. On The Networks
  6151.  
  6152.  
  6153. Where Have All The Sources Gone
  6154.  
  6155.  
  6156.  
  6157.  
  6158. Sydney S. Weinstein
  6159.  
  6160.  
  6161. Sydney S. Weinstein, CDP, CCP is a consultant, columnist, lecturer, author,
  6162. professor, and president of Datacomp Systems, Inc., a consulting and contract
  6163. programming firm specializing in databases, data presentation and windowing,
  6164. transaction processing, networking, testing and test suites, and device
  6165. management for UNIX and MS-DOS. He can be contacted care of Datacomp Systems,
  6166. Inc., 3837 Byron Road, Huntingdon Valley, PA 19006-2320 or via electronic mail
  6167. on the Internet/Usenet mailbox syd@DSI.COM (dsinc!syd for those who cannot do
  6168. Internet addressing).
  6169.  
  6170.  
  6171. I used to blame the dearth of source postings on the summer doldrums, or on
  6172. spring break, or other such college holidays. However, it is now late
  6173. September, and the wires have been exceedingly idle. The sources in the
  6174. mainstream news groups are very slow in coming. Has everyone gone off to learn
  6175. X? The X Window System source news group has had considerable action. I
  6176. normally don't review items in that group, as CUJ is not a pure UNIX journal.
  6177. Should I?
  6178. I currently keep my ears tuned and my eyes wandering over the doings in the
  6179. comp.sources.games, comp.sources.misc, comp.sources.reviewed,
  6180. comp.sources.unix and alt.sources groups. There are others. Most of those
  6181. relate to a specific computer system, such as the Acorn, Amiga, Apple2, Atari,
  6182. Mac or HP48 (yes, the HP48 hand-held calculator has its own news group). The
  6183. only other source news group with considerable traffic of nontrivial programs
  6184. is comp. sources.x.
  6185. I leave it to your decision. Send me electronic mail indicating whether you
  6186. think I should cover the X news group. If you cannot send electronic mail,
  6187. send your letter to the address listed in my biography. Please mark the
  6188. outside of the envelope that this is a vote on the X news group issue.
  6189.  
  6190.  
  6191. Rich?
  6192.  
  6193.  
  6194. Part of what has led me to wonder where everyone has gone is that there have
  6195. been no postings at all in comp.sources.unix since June. Rich's honeymoon has
  6196. long since been over, and the last comment from him in regards to the queue of
  6197. postings is also several months old. This probably explains the increase in
  6198. traffic in comp.sources.misc compared to before Rich took his extended hiatus.
  6199.  
  6200.  
  6201. Slow Reviews
  6202.  
  6203.  
  6204. Even comp.sources.reviewed is slow to produce, although from its status
  6205. reports the problem is in getting corrected packages back from their authors.
  6206. The only posting was an updated version of deliver, Chip Salzenberg's system
  6207. to handle incoming electronic mail. It can forward the mail based on content,
  6208. store it in a set of folders, reply with requested information to the sender,
  6209. or anything that can be described as a shell script. It is extremely flexible
  6210. and portable. It runs under almost any UNIX system and even runs on UNIX
  6211. clones, including Coherent. It does require mail transport software that can
  6212. support delivery of messages to a process. This includes Smail 2.x, Smail 3.x,
  6213. Sendmail, or the SCO XENIX mail system. It currently does not support MMDF.
  6214. F105MIdeliver, Version 2.1.06 is Volume 1, Issues 10-14, with Patch 7 to take
  6215. it to version 2.1.07 in Volume 1, Issue 15.
  6216.  
  6217.  
  6218. Misc Still Abounds
  6219.  
  6220.  
  6221. What appears to be happening is that everyone is fed up with the tested source
  6222. groups, and going to the immediate post source group, comp.sources.misc.
  6223. Unfortunately, this also leads to more patches being posted, as the testing
  6224. now occurs in public with everyone seeing the bugs instead of in private with
  6225. only the author and the group reviewers. However, even the traffic in this
  6226. group is down. Approximately six megabytes of posting of about 40 packages
  6227. were made over the past two months. Some of the highlights are:
  6228. Dave Mack <csu@alembic.acs.com> posted compress v4.1 as Volume 20, Issues 64
  6229. and 65. However this started some controversy. It appears that the bug fixes
  6230. to compress v4.0 never found their way into compress v4.1, and there is a
  6231. competing version of compress v4.3. Someone is supposed to post a merged
  6232. version of compress v4.1 and compress v4.3, along with checking for the bug
  6233. fixes, but it has not happened yet. I'll keep an eye out for it. In the
  6234. meantime, compress is the main tool UNIX systems use to shrink files via LZW
  6235. compression. Unlike the MS-DOS arc style programs, it does not keep a
  6236. directory of what it compresses, it just compresses the file itself. It then
  6237. renames it to the file name plus a .Z suffix. The compression table size is
  6238. tunable from 12 to 16 bits to achieve the best compression. compress is
  6239. portable to many systems, and I use a version on MS-DOS that supports the same
  6240. 16-bit tables as my UNIX versions. A patch was issued for compress to fix some
  6241. problems as Volume 20, Issue 74.
  6242. Do you need to mix Fortran and C code in the same program? Ever try and call a
  6243. Fortran routine from C? Burkhard Burow <burow@cernvax.cern.ch> must have had
  6244. to often enough that he developed a tool to make the task easier and machine
  6245. independent. His tool, cfortran, was posted as Volume 20, Issues 66 and 67. It
  6246. includes a header file to allow for easy prototyping of Fortran functions in C
  6247. and helps generate C wrappers for C functions so they can be called from
  6248. Fortran. It also includes test and demonstration programs.
  6249. In a local area network of machines, it is important that they all keep the
  6250. same time of day clock. Otherwise, the machine with the slow clock might
  6251. modify a file, and the machine with the fast clock will think that the file is
  6252. not new compared to some other file it recently modified. This can cause all
  6253. kinds of problems with file sharing, NFS, make files, and other tasks that
  6254. require time stamps for synchronization. On the Internet, we use a special
  6255. purpose program called ntp (Network Time Protocol) to set the clocks of all
  6256. machines to within several milliseconds of "standard time." This same program
  6257. then keeps the clocks tracking to that tolerance the entire time the machines
  6258. are up and running. Clarence Dold <dold@mitisft.convergent.com> thinks this is
  6259. overkill for small isolated local networks and has developed a tool that
  6260. allows for slaving several systems to a master with accuracy of a couple of
  6261. seconds instead of milliseconds. The master is kept accurate by whatever
  6262. method is appropriate (nothing, calling NBS on the phone periodically,
  6263. checking it by hand periodically). The rest use his remtime, Volume 20, Issue
  6264. 69, to set their clocks at boot time and periodically resync them to the
  6265. master system while running.
  6266. Jonas Yngvesson <jonas-y@isy.liu.se> has provided supp v2.1, a library for
  6267. rendering scenes with 3-D objects for Volume 21, Issues 26-33. supp v2.1
  6268. includes more rendering modes (Phong, Gourard and line), support for rendering
  6269. into other places than files (including pixmaps), larger oversampling to
  6270. reduce aliasing, two new object primitives (cone and prism), two new shaders
  6271. (strauss and wood), a full polygon clipper, and the ability to remove
  6272. subobjects and surfaces from objects.
  6273. Another simple menu system, supporting text menus, was contributed by Ted
  6274. Wisniewski <ted@oz.plymouth.edu> for Volume 22, Issues 98 and 99. This is an
  6275. updated release of the PSCmenu package. It replaces one released last month
  6276. that replaced one that was last released as Volume 16, Issue 99. The menus are
  6277. kept in simple text flies and can support execution of any UNIX command.
  6278. Want to brag about how fast your new computer really is? One of the
  6279. traditional benchmarks for supercomputers is Lawrence Livermore National
  6280. Laboratory's Livermore Loops program in Fortran. As part of a research project
  6281. to compare Fortran and C for numerical computation, Martin Fouts translated
  6282. the benchmark into C for the NASA Ames Research Center. Cloops was contributed
  6283. for Volume 21, Issues 36-38. It implements the 24 loop version of the
  6284. Livermore Fortran Kernels as described in The Livermore Fortran Kernels: A
  6285. Computer Test of the Numerical Performance Range by Frank H. McMahon (LLNL
  6286. UCRL-53745).
  6287. One of the features of the Berkeley Software Distribution (BSD)-derived UNIX
  6288. systems is the support in the operating system for a feature that determines
  6289. which program will execute a shell file by reading the first line of the file.
  6290. If this line contains #! as the first two characters, then the rest of the
  6291. line is the name of the interpreter to execute this script. AT&T-derived UNIX
  6292. systems before SVR4 do not support this feature, but it can be emulated by
  6293. changes to the exec family of functions. David J. MacKenzie <djm@eng.umd.edu>
  6294. has done just that with libiexec for Volume 21, Issue 39. He has provided
  6295. replacements for the exec family of functions that will check the contents of
  6296. the first two bytes of the file to be executed, and invoke the proper
  6297. interpreter if the first two bytes match the #! magic characters.
  6298. Brandon S. Allbery <allbery@ncoast.org> has updated his malloc debugging
  6299. package and reissued it for Volume 21, Issue 41. The last release was in 1987.
  6300. It now catches writes to either side of malloc'd memory, and checks the pool
  6301. for consistency on each call to malloc, free, realloc, and calloc. It also
  6302. supports a traceback printout of the stack when a malloc check fails, traps
  6303. bus and segmentation violations as if they were bad pointer references and
  6304. dumps the malloc pool, and uses environment variables to control many features
  6305. of the debugging session.
  6306. Warren Tuckers's <wht@n4hgf.mtpark.ga.us> extended call utility package, ECU,
  6307. underwent extensive changes recently. ECU is an asynchronous communications
  6308. program for UNIX and XENIX systems. It incorporates a rich procedural language
  6309. and several file transfer protocols. Revision 3.10 adds support for gcc,
  6310. non-ANSI consoles, and xterms as well as a better configuration system. It was
  6311. issued in 37 parts as Volume 21, Issues 53-89. In addition several patches
  6312. were released. Patch 1, Volume 22, Issues 19-21, enhances the support for ISC
  6313. UNIX. Patch 2, Volume 22, Issue 22, fixes a problem with the nap () system
  6314. call. Patch 3, Volume 22, Issues 70-72, adds support for SunOS 4.1 on Sparcs
  6315. and SVR4. Patch 4, Volume 22, Issues 77 and 78, fixes some SVR4 related
  6316. problems. Patch 5, Volume 22, Issues 90-94, enhances ECU's portability even
  6317. further and adds supports for the fas drivers. Patch 6, Volume 22, Issue 101,
  6318. is just a bug fix for some release problems. A new manual was issued for
  6319. version 3.10 as Volume 21, Issues 90-93. This manual documents the current
  6320. version and runs over 100 pages in five chapters. Now at version 3.16, by way
  6321. of the six patches, ECU now supports many different platforms, not just the
  6322. SCO UNIX/XENIX platforms. With ECU, your UNIX system can have a
  6323. telecommunications program to rival those on the PCs.
  6324. Oishii Ichigo <whiz@well.sf.ca.us> submitted midilib, a library of routines to
  6325. read and write files writing in the MIDI Manufacturer's Association standard
  6326. format. It runs on UNIX, Macs and PCs and may be portable to other systems. It
  6327. can give a verbose textual listing of a MIDI file, convert format 1 multitrack
  6328. files to format 0, and assist in writing MIDI files. It was contributed for
  6329. Volume 21, Issues 96 and 97.
  6330. Benson I. Margulies <benson@odi.com> modified the original BSD indent program,
  6331. which understood only C sources, to understand how to pretty-print C++ sources
  6332. as well. His modified indent was issued as Volume 21, Issues 98-100.
  6333. Making changes to binary files can be difficult. Help is available via the
  6334. Binary Editor and Viewer from Peter Reilley <pvr@wang.com>. BEAV allows for
  6335. changes, insertion, and deletion (yes, changing the size of the file). It can
  6336. enter or display data in hex, octal, decimal, binary, ASCII, or EBCDIC
  6337. formats. It can group by bytes, words, or long words in either little-endian
  6338. or big-endian byte ordering. BEAV is Volume 22, Issues 10-18.
  6339. A much shorter program is prtscrn from Chip Rosenthal
  6340. <chip@chinacat.unicom.com>. It captures the screen contents of an SCO Console
  6341. MultiScreen and sends it to stdout. prtscrn is Volume 22, Issue 27.
  6342. I discussed in a prior column the archie service from archie.mcgill.ca. This
  6343. service provides a database of who is archiving what sources via ftp. Brendan
  6344. Kehoe <brendan@cs.widener.edu> has written a Prospero client to access the
  6345. archie databases without using an interactive process on the remote machine.
  6346. This provides faster, and lower overhead, access to the database for those
  6347. sites that are on the Internet. Archie, version 1.1, is Volume 22, Issues
  6348. 35-39.
  6349. In my April 1990 column I introduced the popi digital darkroom software from
  6350. Rich Burridge <richb@aus.sun.com>. Rich has updated the software and
  6351. re-released it for Volume 22, Issues 40-48. Popi allows arbitrary
  6352. transformations to be interactively applied to digital images. New to this
  6353. release are use of the PBM/PGM/PPM graphics formats, use of Floyd/Steinberg
  6354. dithering for monochrome screens, support for 24-bit color, and many bug
  6355. fixes.
  6356. A new program on the scene is causing many system administrators to pull their
  6357. hair out. Crack v3.2a is a program that performs dictionary searches on the
  6358. UNIX password file. It reports all accounts that have passwords that can be
  6359. compromised by this method. Its intent is twofold. First, to inform the
  6360. average system manager of which accounts could easily be compromised, and,
  6361. second, to weaken the complacency among inexperienced and experienced UNIX
  6362. systems administrators about how secure their passwords are. Crack v3.2a, from
  6363. Alec David Muffett <aem@aber.ac.uk>, is Volume 22, Issues 49-52.
  6364. Raymond Chen <rjc@math.princeton.edu> has contributed wp2x, his program to
  6365. convert WordPerfect version 2.x files to any text-based formatting language.
  6366. The distribution includes configuration files for Tex, LaTex, SCRIPT/GML, and
  6367. troff. WordPerfect 5.x files must be saved in 4.2 format before they can be
  6368. converted by the program.wp2x is Volume 22, Issues 55-57.
  6369. Lutz Prechelt <prechelt@i41s14.ira.uka.de> has developed crefine, a tool to
  6370. add an additional language construct called "refinement," which allows further
  6371. decomposition with symbolic names inside functions for C and C++. crefine,
  6372. Volume 22, Issues 63-66, supports any system that supports C and stdio. It is
  6373. a translator of C source files.
  6374. In one posting, Christian Schlichtherle <chris@attron.ruhr.de> has obsoleted
  6375. three of his own programs. He has released a new, improved version of his
  6376. comment-annotated directory listing program. The new one, with a name that is
  6377. configurable, although he called the package XlsX, obsoletes list, dls, and
  6378. vls. It allows for tieing long description names to the file names and then
  6379. displaying those along with the ls output. XlsX is Volume 22, Issues 83-84.
  6380. Note: this is not an X Window program, even though the name starts with an X.
  6381. Patches were also released to prior packages. Mike McGann's
  6382. <mwm@hasler.ascom.ch> gnuchess 3.1 submission had patch 3, Volume 22, Issue 2,
  6383. issued to improve handling of the opening book.
  6384. Brad Appleton <brad@ssd.csd.harris.com> updated his parseargs package with
  6385. patch 8, Volume 22, Issue 24, to clean up some comments and fix a couple of
  6386. memory leaks.
  6387.  
  6388. David Skoll <dfs@doe.carleton.ca> has issued patch 3 for his remind package as
  6389. Volume 22, Issue 102. remind will notify you of events, appointments, or other
  6390. things you feel you should be reminded about. It can signal you or just send
  6391. mail. Patch 3 adds support for the -f option to support foreground execution
  6392. of queued reminders -- very useful for those running remind from a windowing
  6393. system.
  6394. Parag Patel's <parag@hpsdeb.sde.hp.com> waccoLL(1) parser generator had patch
  6395. 2 issued as Volume 21, Issue 44. It fixes a bug where code fragments in {}
  6396. larger than 1024 bytes caused a dump.
  6397. The SC spreadsheet, (last release by Jeff Buhrt <prslnk!buhrt>) had two
  6398. patches. The first, patch 1, Volume 22, Issues 95 and 96, added cell locking,
  6399. some portability changes, and support for MS-DOS. The second, patch 2, Volume
  6400. 22, Issue 104, fixed a null pointer dereferencing problem. It also added a
  6401. high speed data entry mode, new date formats, better support for labels and
  6402. long strings, saving the last cell accesses for the next access, and several
  6403. additional functions.
  6404. Bill Norcott <norcott@databs.enet.dec.com> again completely reissued his
  6405. iozone benchmark as Volume 22, Issue 29. The changes in V1.10 were to increase
  6406. portability to more platforms.
  6407. Fred Walter <grwalter@watfun.waterloo.edu> has also re-released his newsbreak
  6408. program that automatically unpacks postings in the comp.sources and
  6409. comp.binaries news groups. Version 1.14, Volume 22, Issue 53, now supports 286
  6410. systems and ISC UNIX, and fixes several bugs.
  6411.  
  6412.  
  6413. Games Quiet
  6414.  
  6415.  
  6416. Even the games group is very quiet. Are computers and their usage now getting
  6417. more serious? Or are the students just too busy studying?
  6418. Streets and Alleys solitaire, reviewed last time, was patched in Volume 12,
  6419. Issue 88. John Ramsdell <ramsdell@linus.mitre.org> added two improvements. The
  6420. first adds command aliases for those without a numeric keypad. The second adds
  6421. the ability to save and restore games. This allows for trying things and then
  6422. returning to the saved place if it didn't work.
  6423. Not to be outdone by another, Raymond Chen <rjc@math.princeton.edu> also
  6424. submitted an anagram generator. In October's column I reviewed Morten
  6425. Ronseth's <morten@dcs.qmw.ac.uk> ag2. Raymond's anagram2 uses a user-supplied
  6426. dictionary (such as/usr/dict/words) to limit its output to valid words. Both
  6427. an ANSI C version and a Perl script to convert it to a K&R C version are
  6428. supplied in Volume 12, Issue 89.
  6429. Pacman still lives. Rich Burridge <richb@aus.sun.com> has updated his sidtool,
  6430. a Pacman like game from SunView to Xview. sidtool allows for specifying
  6431. alternate mazes, including oneway paths and tunnels. A very complete version,
  6432. with lots of bells and whistles, sidtool is Volume 12, Issues 90-96.
  6433.  
  6434.  
  6435. Previews From alt.sources
  6436.  
  6437.  
  6438. Even alt.sources is showing the effects of the decline in postings. It's not
  6439. down much, but it is down. Once again, only the highlights -- many more
  6440. programs were posted.
  6441. Hinted at in October, with the release of booz, was the upcoming release of a
  6442. new version of Rahul Dhesi's <dhesi@bsu-cs.bsu.edu> Zoo archiver. Version 2.1
  6443. was released on July 10, 1991, in fifteen parts. It includes improved
  6444. compression, better online help, VAX/VMS file timestamp preservation, faster
  6445. uncompression, and other features. It works on almost any system that supports
  6446. C, including VAX/VMS, UNIX, and MS-DOS.
  6447. For those with Mac format archives, Dik T. Winter <dik@cwi.nl> has provided a
  6448. Mac archive unpacker for unpack, PackIt, StuffIt, Compactor, and most
  6449. StuffItClassic/ StuffItDeluxe archives. It does not deal with password
  6450. protected archives, multifile archives, or compression methods 6 and 8. Unpack
  6451. was posted on July 14, 1991, in two parts.
  6452. Panos Tsirigotis <panos@cs.colorado.edu> has contributed a small socket
  6453. programming library that aids in using Berkeley sockets. It takes care of many
  6454. of the little details in allocating, binding, and connecting sockets. It was
  6455. posted on July 21, 1991, in one part.
  6456. UNIX lacks a queued batch processing system. It has at/atrun and batch, but
  6457. those don't implement a queued system. Alan Saunders <tharr!alan>has
  6458. contributed qbatch, a queued system, on July 23, 1991, in four parts.
  6459. Want to run a mail server? If deliver, mentioned above, is not to your liking,
  6460. try mail-server (or even combine its use with deliver). Posted by Jan-Piet
  6461. Mens <logixwi!jpm> on August 12, 1991, it allows access to a set of files via
  6462. a mail response program. It includes permission lists to restrict which users
  6463. can access which flies.
  6464. Printing PostalNet bar codes on envelopes and labels is made easier with Todd
  6465. Merriman's <toolz!todd> barcode program posted on August 13, 1991. It converts
  6466. zip codes in ASCII to HP Laser Jet graphics commands to print the bar code.
  6467. The ever popular UNIX Pcal, a postscript printer calendar generator, has again
  6468. been updated to version 4.1 by Joe Brownlee <jbr@cblph.att.com> on August 19,
  6469. 1991, in six parts. It now includes better date functionality, better quality
  6470. moon phase calculation, better understanding of where to find calendar event
  6471. files under UNIX, and portability changes.
  6472. The changes required to port GCC 1.40, GAS 1.38.1, and GDB 3.5 from the GNU
  6473. project to SCO XENIX were posted on August 27, 1991, in four parts by Steve
  6474. Blezard <Steve.Bleazard@robobar.co.uk>. This port works on SCO XENIX 386 and
  6475. produces files in the native Microsoft/Intel OMF format. The GNU binutils are
  6476. not used, so the port is compatible with the standard library files.
  6477. If you are stuck with a K&R C compiler and need to compile ANSI C source with
  6478. prototypes, you need unproto from Wietse Venema <wietse@wzv.tue.nl>. Posted on
  6479. September 1, 1991, it will leave K&R C alone, but de-ansify the prototypes of
  6480. any C programs it is passed. It is designed to sit as a pass between the C
  6481. preprocesser and the next stage of the compiler.
  6482.  
  6483.  
  6484.  
  6485.  
  6486.  
  6487.  
  6488.  
  6489.  
  6490.  
  6491.  
  6492.  
  6493.  
  6494.  
  6495.  
  6496.  
  6497.  
  6498.  
  6499.  
  6500.  
  6501.  
  6502.  
  6503.  
  6504.  
  6505.  
  6506.  
  6507.  
  6508.  
  6509.  
  6510.  
  6511.  
  6512.  
  6513.  
  6514.  
  6515.  
  6516. Stepping Up To C++
  6517.  
  6518.  
  6519. Operator Overloading, Part 1
  6520.  
  6521.  
  6522.  
  6523.  
  6524. Dan Saks
  6525.  
  6526.  
  6527. Dan Saks is the owner of Saks & Associates, which offers consulting and
  6528. training in C, C++ and Pascal. He is also a contributing editor of
  6529. Windows/DOS. He serves as secretary of the ANSI C++ committee and is a member
  6530. of the ANSI C committee. Readers can write to him at 287 W. McCreight Ave.,
  6531. Springfield, OH 45504 or by email at dsaks@wittenberg.edu.
  6532.  
  6533.  
  6534. No programming language can be all things to all programers. No matter how
  6535. many features a language has, there's always someone who wants just one more.
  6536. A language like FORTRAN that caters to numerical applications provides integer
  6537. and real numbers in a variety of precisions, and even throws in complex
  6538. numbers, but does nothing for programmers who want rational (exact fractional)
  6539. numbers or set algebras. The list of wants is never ending.
  6540. Although you can't create a language with everything that every programmer
  6541. could ever want, you can design a language that gives programmers the tools to
  6542. create the data types they need[1]. This is the approach taken by C++. As
  6543. explained by Bjarne Stroustrup, the inventor of C++:
  6544. "C++ has no high-level data types and no high-level primitive operations. For
  6545. example, there is no matrix type with an inversion operator or a string type
  6546. with a concatenation operator. If a user wants such a type, it can be defined
  6547. in the language itself. In fact, defining a new general-purpose type or
  6548. application-specific type is the most fundamental programming activity in C++.
  6549. A well designed user-defined type differs from a built-in type only in the way
  6550. it is defined and not in the way it is used" [2].
  6551. C++ programmers create new types by defining classes. A class defines both the
  6552. representation of class objects and the operations that can be performed on
  6553. those objects. C++, like C, has many operators that apply to built-in types.
  6554. For user-defined types to appear as if they were built-in, programmers need
  6555. the ability to define new meanings for operators when they are applied to
  6556. user-defined types.
  6557. For example, C++ has no built-in support for complex numbers, but you can
  6558. define complex numbers as a class:
  6559. class complex
  6560. {
  6561. public:
  6562. complex ();
  6563. ...
  6564. private:
  6565. double r, i; // real and
  6566. // imaginary parts
  6567. };
  6568. Class names are type names in C++, so
  6569. complex c1, c2;
  6570. is all your users need do to declare c1 and c2 as complex variables. But, if
  6571. you implement complex addition as a member function called add, then users
  6572. must write
  6573. c1.add(c2);
  6574. to add c2 to c1, and they will know that complex numbers are not built in. For
  6575. complex numbers to look built-in, users must be able to write
  6576. c1 = c1 + c2;
  6577. or
  6578. c1 += c2;
  6579. In fact, you can make complex numbers look built-in using a feature known as
  6580. operator overloading. This month's column shows how to define and use
  6581. overloaded operators.
  6582.  
  6583.  
  6584. Rational Numbers
  6585.  
  6586.  
  6587. I'll begin my explanation of operator overloading by implementing a class for
  6588. rational numbers (fractions), with values such as 1/2 or -3/4. Whereas
  6589. floating point numbers represent numbers such as 1/3 and 4/9 only
  6590. approximately, rational numbers represent them exactly.
  6591. My rational number class uses long integers to store the numerator and
  6592. denominator of the fraction. The class defines four overloaded binary
  6593. operators: + (addition), -- (subtraction), * (multiplication), and /
  6594. (division), along with constructors and an output function. Listing 1 shows
  6595. the header rational.h that defines the class.
  6596. The declaration of an overloaded operator is just like the declaration of a
  6597. function, except that the name of the function is the keyword operator
  6598. followed by the operator symbol. For example, the member function declaration
  6599. rational operator+(rational r);
  6600. in Listing 1 declares a member function called operator+ that accepts one
  6601. argument of type rational and returns a rational.
  6602. For the most part, there's nothing magical about a function name like
  6603. operator+. Had I named the function add, the function declaration would look
  6604. like just another ordinary member function declaration:
  6605. rational add(rational r);
  6606. You can call operator+ just as you would call any other member function. That
  6607. is, if r1, r2 and r3 are rationals, then
  6608. r3 = r1.operator+(r2);
  6609. adds r1 and r2 using rational::operator+, and then copies the result to r3.
  6610. Obviously, there must be some other advantage to overloading operators,
  6611. because this syntax for calling operator+ is no more readable than
  6612. r3 = r1.add(r2);
  6613. If there is any magic here, it's that for any object x of a class type, the
  6614. expression x+y means x.operator+(y). In other words, for rationals r1 and r2,
  6615. the expression r1+r2 is a familiar shorthand syntax for calling
  6616. r1.operator+(r2).
  6617. Listing 2 shows a small test program that uses rationals, and Listing 3 shows
  6618. the output from that program. Note that this implementation of rational
  6619. numbers doesn't reduce fractions to their simplest form. That is a detail I
  6620. will address later.
  6621. In Listing 2, a rational declaration such as
  6622. rational r2 (3, 5);
  6623.  
  6624. uses the constructor
  6625. rational::rational(long n, long d);
  6626. to initialize r2 with the value 3/5 (three-fifths). Although 3 and 5 are int
  6627. (not long int) constants, a C++ compiler applies integral promotions to the
  6628. arguments if necessary to find a constructor with a signature that matches the
  6629. call. (See my earlier column, "Function Name Overloading," CUJ, Nov. 1991, for
  6630. an explanation of function signatures.)
  6631. Notice that you can call that constructor to create rational numbers on the
  6632. fly for use in expressions. For example, in the statement
  6633. r2 = r2 * rational(2, 3) + r1;
  6634. the subexpression rational(2, 3) calls the constructor to create a temporary
  6635. rational object whose value is 2/3. Aside from the fact that rational
  6636. constants must be written as rational (x, y) instead of x/y, expressions
  6637. involving rationals look like expressions involving primitive types.
  6638. Nearly all the built-in unary and binary operators can be overloaded. Only
  6639. . . * : : ? : sizeof # # #
  6640. cannot. (.* is the dereferencing operator for pointers to members, which I
  6641. have not yet covered in this column. # and ## are preprocessing operators for
  6642. stringizing and pasting.) The ANSI C++ committee is even considering allowing
  6643. overloaded. (dot). The operators that are both unary and binary,
  6644. + - * &
  6645. can be overloaded both ways.
  6646. Overloading operators does not change their precedence or associativity. For
  6647. example,
  6648. r2 = r2 * rational(2, 3) + r1;
  6649. is evaluated as
  6650. r2 = ((r2 * rational(2, 3)) + r1);
  6651. because * has higher precedence than + and + has higher precedence than =. You
  6652. cannot create new operator tokens by overloading. For example, you cannot
  6653. create ** as an exponentiation operator.
  6654. I will present an implementation of the overloaded rational operators shortly.
  6655. But first, I must explain a little about the underlying mechanism of member
  6656. function calls.
  6657.  
  6658.  
  6659. Using The Keyword this
  6660.  
  6661.  
  6662. A class member function operates implicitly on the class object to which the
  6663. function is applied. For example, suppose class T looks like
  6664. class T
  6665. {
  6666. public:
  6667. void f(const T &r);
  6668. void g(const char *s);
  6669. ...
  6670. private:
  6671. int m, n;
  6672. ...
  6673. };
  6674. Then the call
  6675. x.f(y);
  6676. means "apply member function f to object x using y as an argument." Inside the
  6677. body of f, an unqualified reference to a class member, say m, is a reference
  6678. to the m member in the T object for which the member was called. For instance,
  6679. given the previous function call, the assignment in
  6680. void T::f(const T &r)
  6681. {
  6682. ...
  6683. m = r.m * n;
  6684. ...
  6685. }
  6686. multiplies y's m (referring to y through r) times x's n and stores the result
  6687. in x's m. The compiler knows how to find y, because it was passed explicitly
  6688. as the argument r. But how does f know where to find x?
  6689. The member function f actually has a hidden extra argument that's the address
  6690. of the object to which the function applies. A call such as
  6691. x.f(y);
  6692. translates into the C-like function call
  6693. f(&x, y);
  6694. Inside the member function, you can refer to the hidden argument by the
  6695. keyword this. In every member function of class T, the compiler implicitly
  6696. declares this as a pointer whose type is T *const. In other words, the member
  6697. function declaration
  6698. void T::f(int i);
  6699. translates into the C-like declaration
  6700. void f(T *const this, int i);
  6701. Note that the this pointer itself is const, but *this is not const. This means
  6702. you can't alter the pointer, but you can alter the object it addresses.
  6703. Inside the body of a member function, every unqualified reference to a data
  6704. member of T is implicitly prefixed with this->. For example, inside f, the
  6705. statement
  6706. m = r.m * n;
  6707. compiles as if it were written as
  6708. this->m = r.m * this->n;
  6709. Also, if f calls another member function from its own class, such as
  6710. g("hello");
  6711. the call compiles as if it were written as
  6712.  
  6713. g(this, "hello");
  6714.  
  6715.  
  6716. Implementing rational's Operators
  6717.  
  6718.  
  6719. Listing 4 shows the source file rational.cpp that implements the member
  6720. functions for the rational class. All of the operator functions use the same
  6721. implementation strategy:
  6722. 1) create a local rational object called result;
  6723. 2) compute result's numerator;
  6724. 3) compute result's denominator;
  6725. 4) return (a copy of) result.
  6726. The four operator functions apply the formulae shown in Table 1.
  6727. As I explained earlier, a class member function operates implicitly on the
  6728. class object to which the member is applied. The expression
  6729. r1 + r2
  6730. compiles as
  6731. r1.operator+(r2)
  6732. which means "apply member function operator+ to r1 using r2 as the argument."
  6733. In the body of operator+, num and denom not prefixed with r. refer to the num
  6734. and denom of the left operand, and r.num and r.denom refer to the num and
  6735. denom of the right operand.
  6736. Class rational has two constructors: a default (parameter-less) constructor
  6737. and another two-argument constructor that initializes the numerator and
  6738. denominator explicitly. I needed the two-argument constructor to create
  6739. rational number objects with specific values. I used the default constructor
  6740. to declare rational variables when I didn't care about the initial value, as
  6741. in the first line inside the body of each operator function in Listing 4. The
  6742. compiler generates a default constructor only if the class has no other
  6743. constructors. Had I not declared the default constructor explicitly, then the
  6744. class would not have a default constructor, and a declaration like
  6745. rational result;
  6746. would be illegal.
  6747. It turns out that, at least for this application, you don't really need a
  6748. default constructor. By using the expressions for the numerator and
  6749. denominator as arguments to the two-argument constructor, you can compute the
  6750. results in a temporary object and return the temporary. That is, for any
  6751. expressions n and d that are convertible to long int, you can rewrite
  6752. rational result;
  6753. result.num = n;
  6754. result.denom = d;
  6755. return result;
  6756. as simply
  6757. return rational(n, d);
  6758. The simplified implementations for the rational operators appear in Listing 5.
  6759.  
  6760.  
  6761. Assignment Operators
  6762.  
  6763.  
  6764. In addition to using the four overloaded rational operators, the program in
  6765. Listing 2 also applies the assignment operator = to rationals. In fact there
  6766. are two assignments:
  6767. r1 = (r1 + r2) / rational(1, 2);
  6768. r2 = r2 * rational(2, 3) + r1;
  6769. which work as you'd expect, even though the rational class doesn't define
  6770. operator=.
  6771. If a class doesn't define operator=, the compiler generates a default version.
  6772. The generated operator= performs what is called a memberwise copy -- it copies
  6773. each member of the right-hand operand to the corresponding member of the
  6774. left-hand operand. For each member of the class that is itself a class object,
  6775. memberwise copy uses that member's operator=.
  6776. For a class T, the generated assignment operator is declared as either
  6777. T &T:: operator=(const T &);
  6778. or
  6779. T &T::operator=(T &);
  6780. If all of T's members are primitive types or class types with assignment
  6781. operators of the first form, then T's assignment operator also uses that form.
  6782. Otherwise, T's assignment operator uses the second form.
  6783. Notice that both forms of the overloaded assignment operator return a
  6784. reference to a class object. At first, you might expect that assignment has a
  6785. void return. The most common use for assignment is as a single statement like
  6786. r1 = r2;
  6787. But in C++, as in C, assignments are not just statements; they are expressions
  6788. that can be used in other expressions, such as
  6789. if ((r1 = r2) != 0)
  6790. or
  6791. r1 = r2 = r3;
  6792. = is right-associative (grouped from right to left), so the latter statement
  6793. is interpreted as
  6794. r1 = (r2 = r3);
  6795. When applied to built-in types, the = operator yields the value of its left
  6796. operand. The default operator= for a class type preserves this behavior for
  6797. class types.
  6798. Whenever you explicitly overload an assignment operator, you should have the
  6799. function return a reference to its left operand. That is, the return statement
  6800. in an overloaded assignment should be something resembling
  6801. return *this;
  6802. Note that a function returning a reference cannot use
  6803. return this;
  6804. When a function returns a reference, the return statement binds the return
  6805. expression value to the returned reference using the same semantic rules as a
  6806. reference declaration. When you declare a reference such as
  6807. rational &r1 = r2;
  6808. r2 must be a rational or a rational &; it cannot be a rational *. You cannot
  6809. initialize a reference with the address of another rational:
  6810.  
  6811. rational &r1 = &r2;
  6812. because the type of &r2 is rational *. By this rule, the return expression of
  6813. a function that returns a reference must be an object and not a pointer.
  6814. return *this;
  6815. binds the reference to an object, but
  6816. return this;
  6817. is an error because it attempts to bind a reference to a pointer. Listing 6
  6818. shows an implementation for rational::operator= that has the same
  6819. functionality as the default memberwise assignment generated by the compiler.
  6820. Overloaded definitions for the other assignment operators, like += and *=,
  6821. should also return a reference to the left operand. Listing 7 shows the
  6822. definition of class rational extended to include definitions for +=, -=, *=,
  6823. and /=. The implementation of each assignment is very similiar to its
  6824. corresponding binary operator.
  6825. Listing 8 shows an implementation of rational::operator+= and another version
  6826. of rational::operator+ rewritten in terms of rational::operator+=. operator+=
  6827. computes its result directly in the left operand and then returns a reference
  6828. to that operand (by referring to this). operator+ creates a local rational
  6829. called result, initialized by copying this using the (default memberwise) copy
  6830. contructor. operator+ computes result using operator+=, and returns a copy of
  6831. (not a reference to) that result.
  6832. Listing 9 shows an implementation of rational::operator-= written in terms of
  6833. rational::operator-. The operator- in Listing 9 is the same as the one in
  6834. Listing 5. operator-= computes its result in its left operand (*this) using
  6835. both operator- and operator=, and then returns a reference to that left
  6836. operand.
  6837.  
  6838.  
  6839. Reducing Fractions To Simplest Form
  6840.  
  6841.  
  6842. The output from my little test program in Listing 3 shows that the rational
  6843. operators don't reduce fractions to their simplest form. For example the
  6844. fraction 22/10 equals 11/5, and -270/150 equals -9/5. Listing 3 shows that
  6845. each arithmetic operation on a rational makes both the numerator and
  6846. denominator grow. Unless you eliminate the common multiples in the numerator
  6847. and denominator, they will overflow after only a few more operations.
  6848. I solved this problem by adding a private member function rational::simplify.
  6849. The revised class definition appears in Listing 10 and the member function
  6850. definitions appear in Listing 11. simplify divides both the numerator and
  6851. denominator by the greatest common divisor, which is computed by calling gcd.
  6852. gcd is a non-member function because I thinks it's a general-purpose function
  6853. that should eventually go into an application-independent library of math
  6854. functions. (This version of gcd is based on one by Niklaus Wirth[3]. I don't
  6855. know if it's optimal, but it seems to work well for this application.)
  6856. I rewrote all the rational binary operators in terms of their corresponding
  6857. assignment operators. Every assignment operator calls gcd. This assures that
  6858. each arithmetic operation on rationals always leaves the resulting fraction in
  6859. its simplest form. Listing 12 shows the revised output of the test program
  6860. with simplified fractions.
  6861. My technique is an improvement, but it doesn't prevent every avoidable
  6862. overflow. Consider this example:
  6863. (999999/1000000) * (1000000/3)
  6864. The result of this multiply is 333333/1, but the numerator overflows before
  6865. the simplify function reduces the fraction to its simplest form. A more robust
  6866. implementation would eliminate the greatest common divisor before multiplying,
  6867. not after.
  6868. I will continue this discussion of overloaded operators in my next column.
  6869.  
  6870.  
  6871. Inquiring Minds Want To Know...
  6872.  
  6873.  
  6874. I recently received this question via electronic mail:
  6875. Your article on reference types in the C Users Journal (September 1991) struck
  6876. a cord with me, but not as you might expect. I should add that I am a spotty
  6877. reader of CUJ, so I hope I am not dealing with an old issue.
  6878. In your opening remarks, you say "Just as many programmers often pronounce int
  6879. *as "int star," I often pronounce int & as "int ref." I have been programming
  6880. in C++ for about a year now, and have been following it with interest longer
  6881. than that. And, I must admit, with not a little embarrassment, that I DON'T
  6882. KNOW HOW TO PRONOUNCE IT! Is it "C Plus Plus" (which I always use), or "C
  6883. Incremented"? My "intellectually minded" colleagues and I have been arguing
  6884. this for at least a year. I have never seen a definitive statement about this
  6885. very important topic.
  6886. Could you please answer this, and put my mind to ease? Maybe even publish the
  6887. answer. Thanks in advance.
  6888. David D. Hathaway
  6889. 762 N. Ripley St.
  6890. Alexandria, VA 22304
  6891. 76104.1042@CompuServe.COM
  6892. It's pronounced "C plus plus." That's the way I've heard the inventor Bjarne
  6893. Stroustrup pronounce it, and he says so in his latest book[2].
  6894. References
  6895. [1] Jagger, Mick and Keith Richard, "You Can't Always Get What You Want," Let
  6896. It Bleed. Gideon Music, 1968.
  6897. [2] Stroustrup, Bjarne, The C++ Programming Language, 2nd. ed. Addison-Wesley,
  6898. 1991.
  6899. [3] Wirth, Niklaus, Algorithms + Data Structures = Programs. Prentice-Hall,
  6900. 1976.
  6901. Table 1
  6902. a c a * d + b * c
  6903. - + - = -------------
  6904. b d b * d
  6905.  
  6906. a c a * d - b * c
  6907. - - - = -------------
  6908. b d b * d
  6909.  
  6910. a c a * c
  6911. - * - = -----
  6912. b d b * d
  6913.  
  6914.  a
  6915.  -
  6916.  b a * d
  6917. ----- = -----
  6918.  c b * c
  6919.  -
  6920.  d
  6921.  
  6922.  
  6923. Listing 1 (rational.h)
  6924. #include <stdio.h>
  6925.  
  6926. class rational
  6927. {
  6928. public:
  6929. rational() { }
  6930. rational(long n, long d) : num(n), denom(d) { }
  6931. rational operator+(rational r);
  6932. rational operator-(rational r);
  6933. rational operator*(rational r);
  6934. rational operator/(rational r);
  6935. void put(FILE *);
  6936. private:
  6937. long num, denom;
  6938. };
  6939. // End of File
  6940.  
  6941.  
  6942. Listing 2 (test1.cpp)
  6943. #include <stdio.h>
  6944. #include "rational.h"
  6945.  
  6946. int main()
  6947. {
  6948. int i;
  6949. rational r1 (1, 2); // r1 = 1/2;
  6950. rational r2 (3, 5); // r2 = 3/5;
  6951.  
  6952. for (i = 0; i < 3; ++i)
  6953. {
  6954. printf("r1 = ");
  6955. r1.put(stdout);
  6956. putchar('\n');
  6957. printf("r2 = ");
  6958. r2.put(stdout);
  6959. putchar('\n');
  6960. r1 = (r1 + r2) / rational(1, 2);
  6961. r2 = r2 * rational(2, 3) + r1;
  6962. }
  6963. return 0;
  6964. }
  6965.  
  6966. // End of File
  6967.  
  6968.  
  6969. Listing 3
  6970. r1 = (1/2)
  6971. r2 = (3/5)
  6972. r1 = (22/10)
  6973. r2 = (-270/150)
  6974. r1 = (1200/1500)
  6975. r2 = (-1350000/675000)
  6976.  
  6977.  
  6978. Listing 4 (rational.cpp)
  6979. #include "rational.h"
  6980.  
  6981.  
  6982. rational rational::operator+(rational r)
  6983. {
  6984. rational result;
  6985. result.num = num * r.denom + r.num * denom;
  6986. result.denom = denom * r.denom;
  6987. return result;
  6988. }
  6989.  
  6990. rational rational::operator-(rational r)
  6991. {
  6992. rational result;
  6993. result.num = num * r.denom - r.num * denom;
  6994. result.denom = denom * r.denom;
  6995. return result;
  6996. }
  6997.  
  6998. rational rational::operator*(rational r)
  6999. {
  7000. rational result;
  7001. result.num = num * r.num;
  7002. result.denom = denom * r.denom;
  7003. return result;
  7004. }
  7005.  
  7006. rational rational::operator/(rational r)
  7007. {
  7008. rational result;
  7009. result.num = num * r.denom;
  7010. result.denom = denom * r.num;
  7011. return result;
  7012. }
  7013.  
  7014. void rational::put(FILE *f)
  7015. {
  7016. fprintf(f, "(%1d/%1d)", num, denom);
  7017. }
  7018.  
  7019. // End of File
  7020.  
  7021.  
  7022. Listing 5 (rational.cpp)
  7023. #include "rational.h"
  7024.  
  7025. rational rational::operator+(rational r)
  7026. {
  7027. return rational(num * r.denom + r.num * denom,
  7028. denom * r.denom);
  7029. }
  7030.  
  7031. rational rational::operator-(rational r)
  7032. {
  7033. return rational(num * r.denom - r.num * denom,
  7034. denom * r.denom);
  7035. }
  7036.  
  7037. rational rational::operator*(rational r)
  7038. {
  7039. return rational(num * r.num, denom * r.denom);
  7040. }
  7041.  
  7042.  
  7043. rational rational::operator/(rational r)
  7044. {
  7045. return rational(num * r.denom, denom * r.num);
  7046. }
  7047.  
  7048. void rational::put(FILE *f)
  7049. {
  7050. fprintf(f, "(%1d/%1d)", num, denom);
  7051. }
  7052.  
  7053. // End of File
  7054.  
  7055.  
  7056. Listing 6 (operator=)
  7057. rational &rational::operator=(const rational &r)
  7058. {
  7059. num = r.num;
  7060. denom = r.denom;
  7061. return *this;
  7062. }
  7063.  
  7064. // End of File
  7065.  
  7066.  
  7067. Listing 7 (rational.h)
  7068. #include <stdio.h>
  7069.  
  7070. class rational
  7071. {
  7072. public:
  7073. rational() { }
  7074. rational(long n, long d) : num(n), denom(d) { }
  7075. rational operator+(rational r);
  7076. rational operator-(rational r);
  7077. rational operator*(rational r);
  7078. rational operator/(rational r);
  7079. rational &operator+=(rational r);
  7080. rational &operator-=(rational r);
  7081. rational &operator*=(rational r);
  7082. rational &operator/=(rational r);
  7083. void put(FILE *);
  7084. private:
  7085. long num, denom;
  7086. };
  7087.  
  7088. // End of File
  7089.  
  7090.  
  7091. Listing 8 (operator+=)
  7092. rational &rational::operator+=(rational r)
  7093. {
  7094. num = num * r.denom + r.num * denom;
  7095. denom *= r.denom;
  7096. return *this;
  7097. }
  7098.  
  7099. //
  7100. // operator+ written in terms of operator+=
  7101.  
  7102. //
  7103. rational rational::operator+(rational r)
  7104. {
  7105. rational result(*this);
  7106. return result += r;
  7107. }
  7108. // End of File
  7109.  
  7110.  
  7111. Listing 9 (operator-)
  7112. rational rational::operator-(rational r)
  7113. {
  7114. return rational(num * r.denom - r.num * denom,
  7115. denom * r.denom);
  7116. }
  7117.  
  7118. //
  7119. // operator-= written in terms of operator-
  7120. //
  7121. rational &rational::operator-=(rational r)
  7122. {
  7123. return *this = *this - r;
  7124. }
  7125. // End of File
  7126.  
  7127.  
  7128. Listing 10 (rational.h)
  7129. #include <stdio.h>
  7130.  
  7131. class rational
  7132. {
  7133. public:
  7134. rational() { }
  7135. rational(long n, long d) : num(n), denom(d) { }
  7136. rational operator+(rational r);
  7137. rational operator-(rational r);
  7138. rational operator*(rational r);
  7139. rational operator/(rational r);
  7140. rational &operator+=(rational r);
  7141. rational &operator-=(rational r);
  7142. rational &operator*=(rational r);
  7143. rational &operator/=(rational r);
  7144. void put(FILE *);
  7145. private:
  7146. long num, denom;
  7147. void simplify();
  7148. };
  7149. // End of File
  7150.  
  7151.  
  7152. Listing 11 (rational.cpp)
  7153. #include <stdlib.h>
  7154. #include "rational.h"
  7155.  
  7156. rational &rational::operator+=(rational r)
  7157. {
  7158. num = num * r.denom + r.num * denom;
  7159. denom *= r.denom;
  7160. simplify();
  7161.  
  7162. return *this;
  7163. }
  7164.  
  7165. rational &rational::operator-=(rational r)
  7166. {
  7167. num = num * r.denom - r.num * denom;
  7168. denom *= r.denom;
  7169. simplify();
  7170. return *this;
  7171. }
  7172.  
  7173. rational &rational::operator*=(rational r)
  7174. {
  7175. num *= r.num;
  7176. denom *= r.denom;
  7177. simplify();
  7178. return *this;
  7179. }
  7180.  
  7181. rational &rational::operator/=(rational r)
  7182. {
  7183. num *= r.denom;
  7184. denom *= r.num;
  7185. simplify();
  7186. return *this;
  7187. }
  7188.  
  7189. rational rational::operator+(rational r)
  7190. {
  7191. rational result(*this);
  7192. return result += r;
  7193. }
  7194.  
  7195. rational rational::operator-(rational r)
  7196. {
  7197. rational result(*this);
  7198. return result -= r;
  7199. }
  7200.  
  7201. rational rational::operator*(rational r)
  7202. {
  7203. rational result(*this);
  7204. return result *= r;
  7205. }
  7206.  
  7207. rational rational::operator/(rational r)
  7208. {
  7209. rational result(*this);
  7210. return result /= r;
  7211. }
  7212.  
  7213. void rational::put(FILE *f)
  7214. {
  7215. fprintf(f, "(%1d/%1d)", num, denom);
  7216. }
  7217.  
  7218. long gcd(long x, long y)
  7219. {
  7220. x = labs(x);
  7221.  
  7222. y = labs(y);
  7223. while (x != y)
  7224. {
  7225. if (x < y)
  7226. y -= x;
  7227. if (y < x)
  7228. x -= y;
  7229. }
  7230. return x;
  7231. }
  7232.  
  7233. void rational::simplify()
  7234. {
  7235. long x = gcd(num, denom);
  7236. num /= x;
  7237. denom /= x;
  7238. }
  7239.  
  7240. // End of File
  7241.  
  7242.  
  7243. Listing 12
  7244. r1 = (1/2)
  7245. r2 = (3/5)
  7246. r1 = (11/5)
  7247. r2 = (-9/5)
  7248. r1 = (4/5)
  7249. r2 = (-2/1)
  7250.  
  7251.  
  7252.  
  7253.  
  7254.  
  7255.  
  7256.  
  7257.  
  7258.  
  7259.  
  7260.  
  7261.  
  7262.  
  7263.  
  7264.  
  7265.  
  7266.  
  7267.  
  7268.  
  7269.  
  7270.  
  7271.  
  7272.  
  7273.  
  7274.  
  7275.  
  7276.  
  7277.  
  7278.  
  7279.  
  7280.  
  7281.  
  7282.  
  7283.  
  7284.  
  7285. Illustrated C
  7286.  
  7287.  
  7288. A Portable Menu Compiler
  7289.  
  7290.  
  7291.  
  7292.  
  7293. Leor Zolman
  7294.  
  7295.  
  7296. A long time ago, Leor Zolman wrote and distributed the BDS C Compiler for CP/M
  7297. (what's that?). Following a several-year hiatus from computer-compulsiveness
  7298. to learn some people skills, he got married, dragged his disbelieving wife to
  7299. Kansas and joined the staff of R&D Publications, Inc. Two years later his wife
  7300. has almost forgiven him. You can reach him at leor@rdpub.com or
  7301. uunet!bdsoft!rdpub!leor.
  7302.  
  7303.  
  7304. Here at R&D Publications, we do most of our internal data processing on a
  7305. single SCO XENIX/386 system running the latest available releases of the
  7306. Informix database system for XENIX. At any time, there may be up to 30 users
  7307. sharing the system. Many of those users run under the FACET/TERM software
  7308. package to launch multiple login sessions on their individual serial
  7309. terminals. Thus it is not unusual to see up to 50 logical users, and perhaps
  7310. up to 150 system processes, active at any single point during the business
  7311. day. Despite the load, the system response time experienced by our users is
  7312. pretty darn good.
  7313. And now for the punch line: our CPU is just an inexpensive 386/25 Taiwanese
  7314. clone. Even with 12Mb of RAM, system units such as this one sell for about
  7315. half the price of the 10Mb hard disk drive I purchased in 1980. My intention
  7316. isn't to tout the cost-effectiveness of imported hardware; I just think it is
  7317. remarkable how well a contemporary entry-level CPU can be made to perform. A
  7318. system load such as ours could have brought even a minicomputer system to its
  7319. knees not so long ago.
  7320. One way we've managed to streamline our system is to eliminate as many
  7321. CPU-intensive tasks as possible from the daily during-business-hours load. We
  7322. did this partially through the design of a general-purpose sequential
  7323. overnight job spooler. From among the set of tasks that used to be routinely
  7324. run during the day, we looked for the worst bottlenecks. We then modified the
  7325. shell scripts that control these programs to allow users to schedule the
  7326. programs for overnight execution.
  7327. The CMENU menu system I shall be describing in this series of columns was
  7328. originally created as part of our effort to reduce the overall daily system
  7329. load. In the end, CMENU also brought some welcome spinoffs: it significantly
  7330. reduced the Technical Department's maintenance requirements, and enhanced the
  7331. usability, speed, and efficiency of the menu system for our users.
  7332.  
  7333.  
  7334. The 50-Percent Solution
  7335.  
  7336.  
  7337. R&D's entire internal business management system revolves around menus. All
  7338. the menus stem from two root menus we invoke from the system prompt. The first
  7339. of these two menus contains all invoice processing and customer-related tasks.
  7340. The second main menu handles our advertising subsystems and personal utilities
  7341. (E-mail, calendar maintenance, business letter generation, etc.). Counting all
  7342. the submenus and sub-sub-menus, there are roughly 300 distinct menu options in
  7343. the system. Many of these are shell scripts that manage standardized parameter
  7344. entry and invoke Informix report generators.
  7345. Before CMENU, this entire menu system ran under Informix's own menu-processing
  7346. scheme. Menu headers and their associated lists of menu items existed as
  7347. database tables in a master/detail relationship. The mechanism supplied by
  7348. Informix for creating and maintaining the menu system relied on a screen form
  7349. built to work with Informix's general-purpose table query tool, Perform. This
  7350. screen, unfortunately, allows only one menu selection to appear on the screen
  7351. at a time, which made on-line searches for a particular item in the menu
  7352. hierarchy a repetitive, time-consuming operation.
  7353. Informix integrated their menu-maintenance programs into their new-generation
  7354. SQL runtime module, but imposed the limitation that each database can contain
  7355. only a single associated menu system. Since there were two distinct menu
  7356. hierarchies which we used with our one major database, we had to create a
  7357. dummy database just to support the existence of a second independent menu
  7358. hierarchy. Each time a user invoked a menu, the menu program launched two
  7359. system processes. The first was an SQL interpreter to run the user-interface
  7360. portion of the menu, and the second was a back-end SQL engine that interfaced
  7361. with the actual database. Then, in the cases where we had logically isolated
  7362. some additional menu subsystems from the main menus (as when developing new
  7363. subsystems or when we had not yet ported an existing application from our old
  7364. Informix 3.3 system and the menus were still in the old format), any
  7365. invocation of an external (non-integrated) submenu from the main menu system
  7366. meant that
  7367. all the processes from the main menu were still active,
  7368. a new set of processes was launched to handle the new menu, and
  7369. yet more processes came along when the user made a selection from the new
  7370. menu!
  7371. Clearly, this was not an effective use of system resources.
  7372. In contrast to the clumsy internal implementation and maintenance difficulties
  7373. of Informix's menu system, the end-user interface portion of the Informix
  7374. system is remarkably clean, intuitive, and generally user friendly. I chose
  7375. largely to retain the look and feel of their user interface in designing CMENU
  7376. as a replacement for their menu system. This choice allowed changeover at R&D
  7377. to take place with a minimum of retraining. After installation of the new
  7378. CMENU system, a short e-mail message to all users outlining CMENU's few
  7379. extensions to the Informix menu system's user interface sufficed for the
  7380. entire retraining.
  7381.  
  7382.  
  7383. The Best Of Both Worlds
  7384.  
  7385.  
  7386. To the end-user, CMENU-based menus appear roughly the same as their
  7387. Informix-based predecessors. Internally, however, CMENU works quite
  7388. differently. Rather than being built on top of a complex database package,
  7389. CMENU is a standalone system using a pre-compilation scheme to make execution
  7390. (interpretation) of menus as efficient as possible.
  7391. The menu compiler module, named cmenu, compiles an ASCII-format menu
  7392. specification file containing any number of "menu screen" specifications into
  7393. an intermediate binary object format. The menu runner module, rmenu, loads the
  7394. intermediate file(s) into RAM and executes the menu system as specified.
  7395. The advantage of pre-compiled menu specifications is that most of the hard
  7396. work of parsing the source code and compiling it into a form suitable for
  7397. efficient runtime interpretation only happens once, at compilation time. When
  7398. rmenu is invoked, it only has to display the text on the screen, interpret the
  7399. user's keystrokes, process menu navigation commands, and submit the action
  7400. commands associated with selected menu commands to the operating system for
  7401. execution.
  7402. To reduce the number of processes needing to be launched by the runtime menu
  7403. system, rmenu employs a recursive data structure. Separate screens of a menu
  7404. system may be compiled either together in one source file or separately in
  7405. distinct physical source files. At runtime there is no need to spawn
  7406. additional processes just to traverse up and down through the menu structure,
  7407. even when that structure contains separately compiled menu files. A single
  7408. process coordinates all the dynamic loading and menu navigation operations.
  7409. Through a rich, powerful repertoire of options in the CMENU specification
  7410. language, a high level of control over menu behavior is possible in response
  7411. to the varying requirements of application programs and shell scripts. Some
  7412. applications, for example, may require a prompt after their completion to
  7413. allow users to read vital information left on the screen immediately before
  7414. the application terminated. Other applications never leave any useful
  7415. information on the screen and so do not require a prompt. Still other
  7416. applications may sometimes require a prompt and sometimes not, based upon
  7417. their exit status code. Any of these cases can be handled easily by CMENU (at
  7418. least the UNIX version) through appropriate use of the specification language.
  7419.  
  7420.  
  7421. Two Programs, Two Personalities
  7422.  
  7423.  
  7424. The cmenu and rmenu programs share a common intermediate menu object format
  7425. and not much else. Each evolved in its own style of programming, necessarily
  7426. distinct from the other due to the disparate natures of the two connected
  7427. tasks.
  7428. The menu compiler, cmenu, operates as an iterative state machine. It processes
  7429. the source file text sequentially, and maintains all state information in
  7430. global data shared by all the token-processing functions of the program.
  7431. To understand why such a scheme is appropriate for the cmenu program, note
  7432. that the CMENU specification language, like C itself, is not block-structured.
  7433. In C, you define all functions at one top level and can't define functions
  7434. within functions (although you can reference functions from within other
  7435. functions). Other constructs in C, such as expressions, do have a recursive
  7436. nature, but there are no such recursive expression structures in a CMENU
  7437. specification. Thus, a sequential approach works well for parsing CMENU source
  7438. code and also makes the program less complicated.
  7439. On the other hand, the sort of simplicity inherent to cmenu wouldn't work for
  7440. rmenu without severely limiting its power. At run time, menus must be callable
  7441. from other menus, and, ideally, there should be no structural limitation on
  7442. the depth to which menus nest. In practice, of course, memory may eventually
  7443. run out.
  7444. Several steps can be taken to increase the practical limit on the number of
  7445. menus or total menu items a single logical menu system can contain. The first
  7446. is to design a longitudinal menu tree structure, to take maximum advantage of
  7447. the dynamic loading features of CMENU. The second is to compile the CMENU
  7448. program using the huge memory model, so array-size limitations are eliminated.
  7449. All my testing was performed with the CMENU programs compiled with the default
  7450. "small" memory model, for efficiency.
  7451. So, one way rmenu differs from cmenu is that rmenu's menu processing functions
  7452. support recursion. There is only one trivial case where rmenu would not employ
  7453. recursion: when a complete menu system consists of only a single menu file,
  7454. and that file contains only a solitary menu definition with no submenu
  7455. references. In any other configuration, recursive calls will always occur.
  7456.  
  7457.  
  7458. ...And At Least Two Operating Systems
  7459.  
  7460.  
  7461.  
  7462. Another distinction between the two programs is platform-dependence. Both
  7463. programs support C compilers of varying ANSI-compliance, but aside from that,
  7464. cmenu's code is pretty much system-independent. Since cmenu simply compiles a
  7465. text file into a binary file, the issue of real-time user interface management
  7466. (i.e., curses) does not arise.
  7467. rmenu, however, must deal with the user's screen. Pandora's box immediately
  7468. opens, because DOS and UNIX systems have completely different ideas of what
  7469. the user's terminal is like. UNIX sees terminals as serial devices, while DOS
  7470. sees them as direct memory-mapped devices (that is an oversimplification, but
  7471. it'll suffice for present purposes).
  7472. A standard utility library for managing the screen -- the curses library -- is
  7473. available with most UNIX-like systems. The curses library represents enough of
  7474. a standard that several PC-based implementations have even popped up. I
  7475. selected a PC curses package that we distribute through our CUG library. The
  7476. package is called, appropriately, "PC Curses" (CUG volume 298), and it was
  7477. written by Jeff Dean (who, by the way, volunteered extensive assistance to me
  7478. in checking out the CMENU package for bugs and portability issues. Thank you
  7479. Jeff!).
  7480. PC Curses allows the CMENU system to be compiled under DOS with very few
  7481. conditionally compiled variations in the terminal-handling code. This is
  7482. possible because Jeff's library uses the same standard function names as the
  7483. UNIX versions of curses.
  7484. There are some minor variations between the UNIX and DOS flavors of curses,
  7485. and there are also other areas of the CMENU system that must be handled
  7486. differently between UNIX/XENIX and DOS. The most irritating one involves the
  7487. idea of "current working directory," or CWD, and how the CWD can change
  7488. unexpectedly after submitting command strings to the respective operating
  7489. system's command processors.
  7490. Under UNIX, a subshell can never alter the parent shell's current working
  7491. directory, period. There are subtle tricks for letting a child process have a
  7492. say in setting the CWD of its parent, but ultimately the parent is always in
  7493. control of its own CWD.
  7494. Not so under DOS. Once you pass an unknown command string to the command
  7495. processor under DOS, you can't know for sure where the CWD will be upon
  7496. return. The current working directory and currently selected drive (two
  7497. distinct global modes, as we'll see later) must be saved before making any
  7498. system calls. Then, upon return from system calls, those settings must be
  7499. restored.
  7500. Finally, screen and terminal characteristics may vary from system to system.
  7501. Features such as lines per screen, column width, and operation of certain keys
  7502. are terminal-dependent. Under UNIX, for example, certain versions of curses
  7503. support predefined cursor key codes and certain others do not. Under XENIX, at
  7504. least one curses library function name is different than the equivalent curses
  7505. function name on other UNIX-variants. CMENU's header files try to handle known
  7506. variations such as this, to keep the program text as uncluttered with
  7507. conditional compilation directives as possible.
  7508. The CMENU system may be compiled interchangeably under either DOS or XENIX
  7509. without requiring any changes to the code; just use the appropriate makefile
  7510. for the target operating system. Figure 1 shows the makefile for DOS. This
  7511. version works specifically with the Borland C++ make program, but can easily
  7512. be adapted to other dialects of make. Figure 2 shows the UNIX/XENIX version of
  7513. the makefile.
  7514. Each makefile uses the C compiler's -D command line option to define the
  7515. target operating system and cause the appropriate variations of
  7516. system-dependent code to be compiled. Several lines at the top of the makefile
  7517. may be customized as needed to configure CMENU properly for the desired target
  7518. environment.
  7519. There may be issues with UNIX-variants (other than XENIX) that I haven't
  7520. foreseen. One area that I know must be changed for other UNIX systems is the
  7521. way the makefile specifies the names of the curses library object files. It
  7522. works as supplied on XENIX, but I can make no other guarantees.
  7523.  
  7524.  
  7525. The CMENU language
  7526.  
  7527.  
  7528. Figure 3 shows a modified BNF description of CMENU's menu definition language.
  7529. This section presents a detailed English description.
  7530. A CMENU description file has one or more menu definitions. The first menu
  7531. appearing in the file is always the main menu for that file, and need not have
  7532. a name. Since additional menus appearing in the same file can only be accessed
  7533. by name via the lmenu (local menu) option in the main menu, all such
  7534. additional menus must be given unique names.
  7535. Each menu definition begins with a menu clause, consisting of the keyword
  7536. menu, an identifier (optional only for the main menu, required otherwise), and
  7537. an optional colon. After the menu clause come any desired menu options,
  7538. followed by the item definitions. The menu definition is complete when the
  7539. endmenu keyword appears.
  7540. A menu system may consist of any number of separate menu files (or compiled
  7541. units). Traversal across compiled units at runtime is totally transparent from
  7542. the user's point of view.
  7543. Selecting and returning from an external menu (that is, a separately compiled
  7544. unit) appears the same as selecting and returning from a local menu, since the
  7545. user receives no indication that another compiled unit has just been
  7546. dynamically loaded. Within a single menu definition, global menu options may
  7547. be specified immediately after the opening menu clause, but before the first
  7548. item has appeared. If such options appear in a menu, they control screen
  7549. processing for the entire menu (but not for any submenus that might be
  7550. invoked). The available menu options and their functions are:
  7551. path -- Defines the default path for all action items in the menu
  7552. escape, noescape -- Tells whether to allow shell escapes
  7553. spacing -- Sets vertical spacing between items (single or double)
  7554. columns -- Tells number of horizontal columns to be used (1 - 6)
  7555. There is code supporting the compilation of a text alignment option, but there
  7556. is currently no runtime support for this option. There didn't seem any real
  7557. need for it. In case someone wants to add customized options to CMENU, I've
  7558. left the code in as a template to provide a starting point for work on such
  7559. extensions.
  7560. After all desired menu options have been specified, the item definitions
  7561. follow. Each item definition begins with an item clause, consisting of the
  7562. keyword item, an optional item identifier, an optional colon, and an optional
  7563. text string (the text string is optional only because the item text may
  7564. alternatively be specified in its own text clause later in the item
  7565. definition).
  7566. Not all components of the item clause are always optional. If the clause does
  7567. not include an item identifier but does include a text string, a colon between
  7568. the item keyword and the text string is required. This keeps the text string
  7569. from getting parsed as an item identifier. When both an identifier and item
  7570. text are present, the colon may be omitted.
  7571. The only strict requirement for each item definition, other than the item
  7572. clause itself, is an action clause. The action clause must be one of the
  7573. following:
  7574. action -- Execute a system command, or set of commands
  7575. lmenu -- Run a local menu (one in the active .mnc unit)
  7576. emenu -- Run an externally compiled .mnc unit
  7577. exit -- Return to the previous menu; if none, exit
  7578. A menu does not require an exit action, since the user may directly exit a
  7579. menu anytime via the e or x runtime keystroke commands. Sometimes, though,
  7580. displaying the exit option on the screen with other menu items can be useful.
  7581. I've included the exit action code to provide that capability.
  7582. Before or after the action code, some item options may also appear. Many of
  7583. these item options have default behaviors that apply in the absence of
  7584. explicit directions. Such defaults are hard-wired at rmenu compile time by
  7585. definitions in the rmenu.h header file. They may be chosen to suit the
  7586. specific requirements of your user environment. Any item options stated
  7587. explicitly in a CMENU specification always override the defaults. These are
  7588. the available item options:
  7589. text -- If the text isn't part of the item clause, a text clause must be
  7590. provided.
  7591. help -- A help message, appearing only when the highlight bar is on the
  7592. associated option.
  7593. path -- A full pathname overrides the default path; a relative pathname is
  7594. appended onto the default path.
  7595. prompt, noprompt -- Controls whether the system pauses for a keystroke
  7596. following termination of an action item.
  7597. pause, nopause -- Equivalent to prompt/noprompt above (I couldn't decide which
  7598. terms were clearer, so I left them both in).
  7599. preclear, nopreclear -- Controls whether an explicit screen clear occurs
  7600. before execution of an action item.
  7601. nextitem -- Tells where to send the highlight after the current item has been
  7602. executed. Options are:
  7603. first -- to the first item in the menu
  7604. last -- to the last item in the menu
  7605. next -- to the next item in sequence
  7606. <ident> -- to the item with the given identifier
  7607. If no nextitem clause is present, the default is for the highlight to remain
  7608. on the item that was just run.
  7609. The path string, if specified, causes a cd command to be pre-pended to the
  7610. action string when running under a UNIX variant. If running under DOS, then
  7611. both a cd and a drive selection operation are performed, to ensure that the
  7612. named drive/directory is current before the associated action statement
  7613. executes.
  7614. Under UNIX, the action clause (with possibly pre-pended cd statement) is
  7615. passed directly to a system() call. DOS does not support multiple commands on
  7616. a single command line the way UNIX does, but some special processing supports
  7617. compound statements under DOS. This code begins by scanning the action text
  7618. for semicolons. If it finds any, then it breaks the action text into a set of
  7619. individual subcommands as delimited by the semicolons. Each subcommand is then
  7620. processed by an independent system() call. This allows several DOS commands to
  7621. be chained together in a single action clause text string.
  7622. Upon completion of all listed actions, the original drive and path are
  7623. restored under DOS. Under UNIX, this isn't necessary, since a child shell
  7624. cannot alter the parent's current directory.
  7625. An item definition implicitly ends when either another item clause or an
  7626. endmenu keyword is encountered.
  7627.  
  7628.  
  7629. CMENU Language Minutiae
  7630.  
  7631.  
  7632. The preceding section sums up the high-level structure CMENU language. There
  7633. remain only a few syntactic details to mention before moving on.
  7634. Tokens in the CMENU language are delimited by one or more of the following:
  7635. any whitespace (space, tab, or newline), ; (semicolon) or , (comma). Figure 4
  7636. and Figure 5 illustrate the format I use, but there is no reason
  7637. (syntactically) that you couldn't create something to rival Don Libes's
  7638. "Obfuscated C Code" winners if that is what you want to do.
  7639. The parser considers a string to be any sequence of printable characters that
  7640. is not a keyword and does not contain any whitespace. Strings are legal as the
  7641. operand of a path, text, or action clause, and as labels where needed. Single
  7642. or double quotes may be used to delimit a string, and, except in the case of
  7643. labels, prudence dictates doing so. The quotes, however, are not strictly
  7644. required if the string contains no whitespace; I have, in a rush, written
  7645. lines such as
  7646.  
  7647. action mail
  7648. with only a token amount of guilt (sorry) for omitting the quotes around mail.
  7649. Within a string, all characters are taken literally, including the backslash
  7650. character (\). There is no provision for specifying control characters. There
  7651. is only one restriction on allowable character codes within a string: single
  7652. and double quote characters cannot both appear within the same string. If you
  7653. need to include double quotes within a string, delimit that string with single
  7654. quotes (and vice-versa).
  7655. If your editor can generate control characters, you can put them into CMENU
  7656. strings. In practice, however, I've never run across the need to insert a
  7657. non-printable character into any CMENU text string.
  7658. Identifiers, used to assign labels to menus and items, follow the same naming
  7659. rules as C variables, with one exception: case is insignificant. Internally,
  7660. all identifier names are represented by lower-case characters.
  7661. Any token beginning with a digit is parsed as the beginning -- and typically
  7662. the end, since meaningful values are all single-digit here -- of a decimal
  7663. integer value, appropriate only as the operand to either the spacing or
  7664. columns options.
  7665. Finally, the # character (except inside a quoted string) denotes a comment.
  7666. All characters from # to the end of the line are ignored. A line may begin
  7667. with #.
  7668.  
  7669.  
  7670. The Saga Of Pathname Delimiters
  7671.  
  7672.  
  7673. Even after years of programming in C under DOS, I still forget to double up
  7674. the backslashes in pathname strings at least haft of the time. For example,
  7675. I'll have a statement at the top of my C program that looks something like:
  7676. #define PATH "c:\foo\bar"
  7677. The compiler sees a string containing c:, a formfeed, oo, a backspace, and ar.
  7678. This is clearly not the intended result. What I really meant to write was:
  7679. #define PATH "c:\\foo\\bar"
  7680. This kind of goof draws no compilation errors; often, it isn't until after
  7681. I've become thoroughly confused and fired up the debugger that I find the
  7682. mistake and kick myself. To keep from having to use the double-backslash
  7683. notation in CMENU source files, I decided not to give the backslash character
  7684. any special meaning in the CMENU specification language. As described above in
  7685. the section on strings, there is no notation for escape sequences and single
  7686. backslashes in strings are taken literally.
  7687. Originally, I had included a feature in cmenu whereby any forward-slash (/)
  7688. characters encountered in path clauses are automatically mapped into the
  7689. paths-delimiter character appropriate for the target operating system (i.e.,
  7690. either a back- or forward-slash.) I did this only for the path clause,
  7691. however, because that is when most CMENU pathnames appear. Later, Jeff Dean
  7692. pointed out to me that pathnames written with forward-slash (/) characters
  7693. under DOS worked just as well as ones with backslash (\) characters! My copy
  7694. of the Waite Group's MS-DOS Bible didn't mention any such equivalence,
  7695. however, so I began to wonder.
  7696. As it turns out, the slash-translation is performed by the C file I/O library
  7697. functions supplied with the various C compiler packages, and not by DOS
  7698. itself. Performing such translation explicitly within a C program for DOS is
  7699. therefore redundant, and I removed my original translation code from CMENU.
  7700. Then, I found that pathnames written with forward-slashes worked correctly in
  7701. path statements, but not in action clauses. Thinking more about it, I ought
  7702. not have been surprised: CMENU action statements are run by simply passing the
  7703. supplied action text to the operating system's own command interpreter, not to
  7704. a C file I/O function. Thus the slash-translation provided in the C file I/O
  7705. library never gets performed, and DOS ends up choking on the forward-slashes.
  7706. The net result of all this is the following rule of thumb: to be absolutely
  7707. safe, always use backslashes. If you prefer to use forward-slashes, then use
  7708. them only in a path clause, never in an action clause.
  7709.  
  7710.  
  7711. A Sample Menu System
  7712.  
  7713.  
  7714. Figure 4 and Figure 5 represent a small-scale menu system illustrating most of
  7715. CMENU's features. I've arbitrarily chosen a UNIX-based example, but a DOS
  7716. version would not be too different.
  7717. In t.mnu (Figure 4), the main menu has no identifier label (OK for the initial
  7718. menu in a file), allows shell escapes, and is to be displayed with double
  7719. spacing on the screen. There are nine items in this main menu.
  7720. The first item illustrates the automatic prompting feature of CMENU that is in
  7721. effect when the DEF_PROMPT symbol (to be covered later in the rmenu section)
  7722. is set to ON_ERROR. This causes rmenu, in the absence of an explicit prompt or
  7723. noprompt option, to prompt after an action has been completed if and only if
  7724. the status returned by that action is non-zero. In this example, a prompt is
  7725. issued only if the user typed an e in response to the prompt displayed by the
  7726. test.sh shell script (see Figure 6). Upon return from test.sh, the highlight
  7727. bar moves over to the item labeled "zot" below.
  7728. Since DOS systems do not provide standardized support for direct return values
  7729. from system calls, the ON_ERROR option for the DEF_PROMPT feature is only
  7730. meaningful under UNIX. Under DOS, DEF_PROMPT may be set to either YES or NO,
  7731. and individual menu items may always override that default by including the
  7732. prompt or noprompt options.
  7733. The second item in the main menu calls up an external menu, t2.mnc, located in
  7734. subdirectory test. Since the item path is given relatively (that is, no
  7735. leading / character or, if it were under DOS, no drive letter either), the
  7736. path is treated as a subdirectory of the current default menu path. Since no
  7737. path clause was specified in the menu options section, the default menu path
  7738. is the current working directory at the moment of rmenu invocation.
  7739. The third and fourth items illustrate different ways to invoke an action.
  7740. Using the exec command (UNIX only) saves a process.
  7741. Item zot (lines 34-38) contains help text. This text is displayed on the
  7742. screen only when the highlight bar is on that item. Note that in order to
  7743. display the double quotes around the word "Zot," the entire help text is
  7744. delimited by single quotes.
  7745. Finally, the prompt command in menu item zot causes a prompt to be issued
  7746. after the action is executed, forcing the user to press a key before the
  7747. information left on the screen is erased and the menu screen is redisplayed.
  7748. The next item simply illustrates how nextitem specifications may be backward
  7749. as well as forward.
  7750. The next item invokes the local menu named bar, appearing at the end of the
  7751. file. The last few items are just filler, to illustrate what happens when the
  7752. total number of items exceeds the capacity of the default screen arrangement.
  7753. Since there are 18 lines available for item information in the standard
  7754. 24-line screen setup, double spacing the items means that only nine would fit
  7755. in the single-column format. If an additional menu item were created by
  7756. cloning the filler items, then rmenu would automatically go into two-column
  7757. mode in order to preserve the double spacing specified in line 13. If the
  7758. spacing were set to 1 or omitted entirely, rmenu would go into single-spacing
  7759. mode and remain in single-column mode. There is a function in rmenu devoted
  7760. entirely to determining the most appropriate screen arrangement for any given
  7761. menu. That function uses a heuristic based upon the number of items in the
  7762. menu and any explicit spacing and column directives given. I'll cover this, in
  7763. gory detail, when discussing the rmenu program later on in the series.
  7764. Following the endmenu keyword, a second local menu named bar is defined. The
  7765. local menu options serve to disable shell escapes and to set the default path
  7766. for actions in this menu to /usr/bin. Since setting a path this way just
  7767. generates a cd statement before running an action, a named action need not
  7768. necessarily reside in the directory specified in a path clause, as long as the
  7769. program can be found somewhere along the default system path. In many system
  7770. paths, the current directory is searched first; if this is the case on your
  7771. system, any executable commands in the directory named by a path clause take
  7772. precedence over similarly named commands residing elsewhere along the system
  7773. path.
  7774. There is nothing startlingly new about the three items in the bar menu. I used
  7775. semicolons instead of newline to separate some of the clauses, just to show
  7776. that it's permissible.
  7777. The second menu file, t2.mnu (Figure 5) also lacks interesting features; it
  7778. was included to complete the illustration of a two-file menu system. Note that
  7779. the default path for all actions is the same as the default path of the
  7780. calling menu (in this case, the path specified by the second item in the first
  7781. menu of the t.mnu file), since menu foo contains no explicit path clause to
  7782. override that default path.
  7783. One final note: an action clause may contain explicit commands to change the
  7784. path, effectively superseding all previous default paths. An extraneous cd
  7785. command may be executed if a path clause is given and an explicit cd command
  7786. is present within the action text. Such extra cd commands generate only a
  7787. negligible quantity of CPU overhead and may be effectively ignored.
  7788.  
  7789.  
  7790. Common Menu
  7791.  
  7792.  
  7793.  
  7794.  
  7795. Data Structures
  7796.  
  7797.  
  7798. There are three header files in the CMENU package. The master header file,
  7799. cmenu.h, contains the symbolic constant definitions (#define statements) that
  7800. control common data structures and operating-system-specific code for both the
  7801. cmenu and rmenu programs. cmenu.h is included by all program modules, so it
  7802. performs the inclusion of the standard C header file, stdio.h.
  7803. Besides including cmenu.h, the cmenu and rmenu programs each have their own
  7804. personal header files, named ccmenu.h and rcmenu.h respectively. Both of these
  7805. header files use the symbolic constants and data types defined in cmenu.h to
  7806. build the actual data structures needed to perform their specific tasks.
  7807. We'll look first at the common, elementary structures defined in cmenu.h, so
  7808. we can see how cmenu and rmenu later build upon them.
  7809.  
  7810.  
  7811. The Basics
  7812.  
  7813.  
  7814. As noted earlier, cmenu and rmenu share the intermediate .mnc file format;
  7815. consequently, most of cmenu.h (Listing 1) is devoted to declarations of the
  7816. structure types used in that format. The two major structures are named MENU
  7817. and ITEM. Within these two structure types, string elements are all defined as
  7818. char arrays (as opposed to pointers), Boolean elements have type BOOL (really
  7819. just char), logical and multiple-choice elements have type char, and numeric
  7820. elements have type int (even though they would never be negative and probably
  7821. never exceed a value of 255).
  7822. The logical elements have three possible values: YES, NO, and DEFAULT (as
  7823. opposed to the Booleans, which can only be TRUE or FALSE). YES and NO values
  7824. appear when an explicit option was written for that element in a menu or item
  7825. definition; the value DEFAULT signifies that no explicit option was written,
  7826. and rmenu should, in that case, perform the actions dictated by a set of
  7827. default action definitions specified in the rcmenu.h header file.
  7828.  
  7829. If a text element is not specified, it is represented as the null string
  7830. (first character is a '\0'). If a numeric element is omitted, the value
  7831. inserted is DEFAULT, as described above.
  7832. A MENU (a typedefed alias for struct menu) contains the following elements:
  7833. title -- The menu title, displayed at the top of a menu screen
  7834. path -- The default action path for all items (absolute or relative)
  7835. nitems -- The number of items present in the menu
  7836. align -- (not used)
  7837. columns -- Number of columns specified for the item display
  7838. spacing -- Spacing specified for the column display
  7839. widest -- Length of the widest item text (in characters)
  7840. escape -- Whether shell escapes are permitted
  7841. An ITEM (really struct item) contains:
  7842. text -- The text to be put up to represent that item (may be truncated if the
  7843. space is needed for multiple columns)
  7844. path -- the path for the item (absolute or relative)
  7845. action -- The text of the command(s) to be submitted to a system() call when
  7846. the item is chosen to be run, orthe name of an external menu if the action is
  7847. emenu
  7848. help -- The text of any help info, put up on the screen when the item is under
  7849. the highlight bar (never truncated)
  7850. pre_clear (logical) -- Whether to clear the screen before the action
  7851. post_clear (logical) -- Whether to clear the screen after the action
  7852. prompt (logical) -- Whether to pause before returning to menu
  7853. acttyp (multi-choice) -- Tells what brand of action to perform (choices:
  7854. command, lmenu, emenu or exit -- represented by the ACT_* symbolic constants)
  7855. lmenunum -- If the acttyp is ACT_LMENU, specifies the index for the specified
  7856. local menu in the menu table
  7857. nextcode (multi-choice) -- Tells how the next item is to be determined
  7858. (choices: first, last, next, or direct -- NXT_* symbols)
  7859. nextitem -- If nextcode is NXT_DIRECT, this contains the index of the next
  7860. item to be highlighted.
  7861. The way that MENU and ITEM objects are combined is not specified in cmenu.h,
  7862. because that combination is different in cmenu than it is in rmenu. For the
  7863. remainder of this installment I'll focus on the cmenu program and its own data
  7864. data structures.
  7865.  
  7866.  
  7867. The .mnc Format
  7868.  
  7869.  
  7870. The format of compiled .mnc files is summarized in Figure 7. Remember, this is
  7871. a binary format, not an ASCII one.
  7872. The first item in an .mnc file is an integer value that tells how many (local)
  7873. menus are defined in the file. Immediately following this count is the MENU
  7874. structure for the first menu, and after that come the ITEM structures for each
  7875. item in the menu. The number of items in each menu is part of the information
  7876. stored in the associated MENU header, so there's no need to store separate
  7877. item counts in the file format.
  7878. That's it; the .mnc format is fairly simple, conceptually. To actually
  7879. generate it, however, requires a bit more complexity... now the real fun
  7880. begins!
  7881.  
  7882.  
  7883. At Last, cmenu
  7884.  
  7885.  
  7886. The cmenu program is essentially a big state machine, whose purpose is to scan
  7887. through the sequential ASCII token stream of one or more CMENU specification
  7888. files and produce a correctly compiled .mnc output file corresponding to each
  7889. input file.
  7890. At the heart of cmenu's operation is a structure named keywords, defined in
  7891. ccmenu.h (Listing 2, lines 190-228). keywords defines a couple of attributes
  7892. to be associated with each possible keyword token. The tokens represent each
  7893. symbol, keyword, or special condition (such as EOF) that the CMENU language
  7894. recognizes.
  7895. The first attribute, keyword, is the text of the keyword itself; for tokens
  7896. that have no printing text associated with them, I've contrived some special
  7897. identifying sequences (see lines 191 and 223-226) that assisted me during
  7898. interactive debugging of the cmenu program.
  7899. The other attribute is a pointer to the processing function called when the
  7900. associated token shows up in the input stream. All token processing functions
  7901. are named do_whatever, where whatever is the name of the token. There are also
  7902. additional do_whatever functions not tied to a particular token, but called
  7903. occasionally under special circumstances.
  7904. An enumeration constant list (lines 43-58) defines a symbolic name for each
  7905. keyword. These symbolic names appear in exactly the same order as their
  7906. corresponding keywords table entries; thus, the symbolic name for each token
  7907. has a value equal to that token's physical index position in the keywords
  7908. array. Since the symbolic values are used to represent tokens during the
  7909. parsing of input text, this arrangement has a side-effect that facilitates
  7910. debugging of the cmenu program: you can keep a running display of the ASCII
  7911. string associated with the most recently parsed token by placing the
  7912. expression
  7913. keywords[token].keyword
  7914. into a watch window.
  7915. For compilers that do not support enumeration constants, the "old-fashioned"
  7916. way of defining a set of sequential symbolic values is shown in lines 60-99.
  7917. This section of conditionally-compiled code is used instead of the enum
  7918. section when __STDC__ (a pre-defined symbolic constant indicating ANSI
  7919. compliance) is undefined.
  7920. There are a couple of key global state variables that describe which portion
  7921. of a menu definition is currently being processed. The first of these,
  7922. in_menu, is a simple Boolean variable telling whether or not a menu definition
  7923. is being processed. in_menu remains FALSE until the first menu clause is
  7924. encountered; from that point on, in_menu is TRUE between each menu clause and
  7925. endmenus keyword, FALSE otherwise.
  7926. The other critical state variable is in_item. This one is set to TRUE every
  7927. time the first item clause of a menu is encountered, and reset to FALSE (along
  7928. with in_menu) each time endmenu is processed.
  7929. Both of these state variables are initialized to FALSE near the start of
  7930. do_menu(), and thereafter their values toggle as necessary under control of
  7931. the appropriate token-processing functions.
  7932. With the keywords structure and state variables all in place, cmenu's main
  7933. processing loop can be frightfully simple. Indeed, lines 71-81 of Listing 3 (a
  7934. partial listing of the source file cmenu1.c) make up this entire loop. The
  7935. code relies, however, on the token scanning function gettok() to parse the
  7936. next little piece of the input stream into a token value and associated detail
  7937. values.
  7938. If the next token is a string, for example, then gettok() returns the token
  7939. T_STRING. The actual text of the string is stuffed into a global char array
  7940. named tparam.
  7941. The main loop can prevent the token-processing functions from having to
  7942. perform a common error-checking test by making sure that, when not currently
  7943. within a menu (i.e., whenever in_menu is FALSE), the menu keyword is the next
  7944. token scanned. Whenever any token other than menu is encountered outside of a
  7945. menu definition, an error is reported (line 75) and processing of the current
  7946. file terminates.
  7947. No other error condition is as universally easy to detect, so any further
  7948. diagnostics are relegated to specific token-processing functions. Lines 78-80
  7949. dispatch control to the appropriate processing function, and check for the
  7950. possibility of a fatal error before continuing on to the next loop iteration.
  7951.  
  7952.  
  7953. Information Economy
  7954.  
  7955.  
  7956. Now let's return to the ccmenu.h header file and investigate some new data
  7957. structures.
  7958. The CMENU language supports forward references in both menu and item label
  7959. references, so we need some way of keeping track of which menus and items have
  7960. been defined so far, which ones have not, and where those references came from
  7961. so they can be resolved when possible, or diagnosed as unresolved reference
  7962. errors otherwise.
  7963. There is also the matter of the menu and item identifier names. There is no
  7964. place for those identifiers in the .mnc file format, since by the time the
  7965. .mnc file is written, each reference to a menu or item has been resolved into
  7966. an integer index value that allows direct access to the menu or item desired
  7967. through a simple indexing operation.
  7968.  
  7969. So, what's needed is a set of data structures that contain the elementary MENU
  7970. and ITEM structures as subsets of larger structures. These larger structures
  7971. add the additional pieces of data necessary to support the compilation
  7972. process, but still allow the essential MENU and ITEM information to be
  7973. extracted, in the required elementary format, when the time comes to write an
  7974. output file.
  7975. The typedefs for just such a set of incremental structures can be found in
  7976. lines 27-37 of Listing 2. The first definition, IINFO, is a structure
  7977. containing just two elements: a name string, and an INFO structure. The second
  7978. typedef, MINFO, is a structure containing a name string, a Processed flag to
  7979. indicate completion of menu processing, a MENU structure describing the
  7980. properties of the menu, and an array of pointers to the IINFO structures that
  7981. make up the individual items associated with the menu.
  7982. The decision to use an array of pointers to structures in the Items array, but
  7983. actual instances for the other structure elements (Item and Menu), actually
  7984. came about as the result of much trial-and-error. The goal was to find an
  7985. ideal balance for this application between memory-efficiency and coding
  7986. clarity. In C, unfortunately, those two properties tend to unfold in inversely
  7987. proportional quantities within any sufficiently complex application. Finding
  7988. the right balance can be a challenging task.
  7989. In order to make efficient use of available memory when the size of an array
  7990. is not fixed at compile time, we need to use dynamic memory allocation to
  7991. obtain exactly the needed quantity of memory, and no more, at runtime. When
  7992. the storage for an object is allocated dynamically, then pointers must be used
  7993. to access the data -- and manipulating pointers to objects is usually more
  7994. complex than manipulating just the objects themselves.
  7995. If more than one level of dynamic arrays are involved, then things get
  7996. positively twisted. In my first design for cmenu, I attempted to use a data
  7997. structure that had multiple levels of dynamically-allocated arrays. (Anyone
  7998. remember the tricky dynamically-allocated arrays from my Mini-Database System
  7999. series in CUJ last year? And that was only one level!) Needless to say, it was
  8000. a mess. I learned, however, that it was still possible to squeeze much good
  8001. use out of CMENU's small-model memory space even without many of the dynamic
  8002. allocation tricks. Most of the memory goes to hold ITEM information, anyway;
  8003. why not restrict dynamic allocation to only the memory needed for ITEM
  8004. structures? The resulting code is much easier to document and understand than
  8005. my earlier multilayered dynamic scheme.
  8006. The MINFO structure applies to a single menu only, so there is an array of
  8007. MAX_MENUS MINFO structures defined in line 235 as MInfo. The dimension of
  8008. MAX_MENUS limits the number of structures that may be defined within a single
  8009. source module; if long menu source files give you memory problems, reducing
  8010. the value of MAX_MENUS offers quick memory relief (but might make it necessary
  8011. to split some large .mnu files into smaller pieces).
  8012. The final major data structure in the cmenu program is the forward-reference
  8013. table, fwd_refs, defined in lines 241-245. This table performs the task of
  8014. tracking forward references to named items in a menu. No such corresponding
  8015. table is needed to handle forward menu references, however. Here is why: The
  8016. order of items within a menu is significant; they should appear on the screen
  8017. in the same order as their specification in the source file. Yet, how is the
  8018. compiler supposed to behave when a reference is made to an as-yet-undefined
  8019. item, one that could appear anywhere from the current point in the menu source
  8020. to the end of the menu? Does the compiler immediately create an item record
  8021. reserved for the future item, and add it to the array of items? If so, then
  8022. the physical order of the items gets messed up (this isn't an obvious
  8023. side-effect; my first stab at this code utilized an IINFO "Processed" flag
  8024. similar to the MINFO flag of the same name, without taking the aforementioned
  8025. phenomenon into consideration. Surprise, I couldn't get forward references to
  8026. work!)
  8027. Rather than creating an IINFO structure when a forward reference in
  8028. encountered, the present scheme simply registers the reference into the
  8029. fwd_refs table (including the line number of the reference, for diagnostic
  8030. purposes), and goes on processing more menu items. Whenever a new labeled item
  8031. definition is encountered, cmenu makes a quick check to see if any references
  8032. have been made to that item label; if found, the references are resolved by
  8033. copying the index number of the new item definition into the location whose
  8034. address was saved in the forward reference table. If any unresolved references
  8035. remain at the end of the menu definition, the precise line number of the
  8036. reference can even be supplied in the error diagnostic. The code that handles
  8037. this is not very complicated, but has proven immensely effective. If I were
  8038. really smart, I would have written it this way in the first place, but then
  8039. I'd have missed another opportunity to play with Turbo Debugger!
  8040. The remaining definitions in ccmenu.h are for miscellaneous scratch pointers,
  8041. global variables, and constants. Next time, we'll journey through the rest of
  8042. cmenu's procedural code.
  8043. Figure 1
  8044. #
  8045. # Make file for CMENU Menu compiler system (DOS version)
  8046. # Developed under Borland C++, but written portably
  8047. #
  8048.  
  8049. ################################################################
  8050. # Primary configuration section
  8051. ################################################################
  8052.  
  8053. CC = bcc # command-line compiler name
  8054.  
  8055. # uncomment only one line in each of following 3 pairs of lines:
  8056.  
  8057. #DEBUG = -v # enable Turbo Debugger info
  8058. DEBUG = # disable debugging
  8059.  
  8060. #BCCOPTS =
  8061. BCCOPTS = -w-pia -A # Borland-specific stuff
  8062.  
  8063. #NEEDSTR = -DNEEDSTR # compile own strstr() definition
  8064. NEEDSTR = # use library version of strstr()
  8065.  
  8066. WILDLIB = wildargs.obj # links wildcard module
  8067.  
  8068. CULIBS = tscurses.lib # curses library
  8069.  
  8070. ##############################################################
  8071. # End of primary configuration section
  8072. ##############################################################
  8073.  
  8074. COPTS = $(BCCOPTS) -DDOS=1 $(DEBUG) $(NEEDSTR)
  8075. CFILES = cmenu1.obj cmenu2.obj cmenu3.obj
  8076. RFILES = rmenu1.obj rmenu2.obj rmenu3.obj rmenu4.obj
  8077.  
  8078. all: cmenu.exe rmenu.exe dmenu.exe
  8079.  
  8080. .c.obj: # For non-Borland MAKE, you may need to
  8081.  $(CC) -c $(COPTS) {$< } # substitute "$*.c" for "{$< }"
  8082.  
  8083. cmenu.exe: $(CFILES)
  8084.  $(CC) $(DEBUG) -ecmenu $(CFILES) $(WILDLIB)
  8085.  
  8086. rmenu.exe: $(RFILES)
  8087.  $(CC) $(DEBUG) -ermenu $(RFILES) $(CULIBS)
  8088.  
  8089. dmenu.exe: dmenu.c cmenu.h
  8090.  $(CC) $(COPTS) -edmenu dmenu.c
  8091.  
  8092. $(CFILES): ccmenu.h cmenu.h makefile
  8093.  
  8094.  
  8095. $(RFILES): rcmenu.h cmenu.h makefile
  8096.  
  8097. Figure 2
  8098. #
  8099. # Make file for CMENU Menu compiler system
  8100. # For use with Unix/Xenix
  8101. #
  8102.  
  8103. CC = cc
  8104.  
  8105. # Configuration options:
  8106.  
  8107. # Uncomment only one of the following two lines, XENIX for XENIX only,
  8108. # UNIX for any non-XENIX system:
  8109. #SYSTEM = UNIX
  8110. SYSTEM = XENIX
  8111. # uncomment one only (first works for XENIX, second for most others):
  8112. CULIBS = -ltcap -ltermcap
  8113. #CULIBS = -lcurses -ltermcap
  8114.  
  8115. # uncomment only ONE of the following 2 lines. The first will
  8116. # compile the included strstr() definition, the second will cause
  8117. # the library version of strstr() to be used.
  8118. NEEDSTR = -DNEEDSTR
  8119. #NEEDSTR =
  8120.  
  8121. #
  8122. # From this point on, no changes should be necessary.
  8123. #
  8124.  
  8125. COPTS = -D$(SYSTEM)=1 $(NEEDSTR)
  8126. CFILES = cmenu1.o cmenu2.o cmenu3.o
  8127. RFILES = rmenu1.o rmenu2.o rmenu3.o rmenu4.o
  8128.  
  8129. all: cmenu rmenu dmenu
  8130.  
  8131. .c.o:
  8132.  $(CC) -c $(COPTS) $<
  8133.  
  8134. cmenu: $(CFILES)
  8135.  $(CC) -o $@ $(CFILES)
  8136.  
  8137. rmenu: $(RFILES)
  8138.  $(CC) -o $@ $(RFILES) $(CULIBS)
  8139.  
  8140. dmenu: dmenu.c cmenu.h
  8141.  $(CC) $(COPTS) -o dmenu dmenu.c
  8142.  
  8143. $(CFILES): ccmenu.h cmenu.h
  8144.  
  8145. $(RFILES): rcmenu.h cmenu.h
  8146.  
  8147. Figure 3
  8148.  CMENU Specification Language
  8149.  ----------------------------
  8150.  
  8151. Explanation of my "loose BNF" form:
  8152.  
  8153.  
  8154.  := reads "is defined as".
  8155.  
  8156.  Terms in <>s are syntactic objects.
  8157.  
  8158.  Terms in CAPITALS are keywords.
  8159.  
  8160.  An item in [] is optional
  8161.  
  8162.  If a set of items is enclosed in {} with 
  8163.  between each item, or a range is shown
  8164.  using the - character, exactly one o
  8165.  those items must be picked.
  8166.  
  8167.  * to the right of an object denotes 0
  8168.  or more occurrences.
  8169.  
  8170.  + to the right of an object denotes 1
  8171.  or more occurrences.
  8172.  
  8173. Wherever a separator is necessary to
  8174.  delimit tokens, the characters ';' and
  8175.  ',' are both valid separators along
  8176.  with the usual whitespace characters
  8177.  (space, tab and newline.)
  8178. ----------------------------------------
  8179.  
  8180. <cmenu-source-file> :=
  8181.  <menu-defn> +
  8182.  
  8183. <menu-defn> :=
  8184.  MENU [<identifer>] :
  8185.  <menu-option> *
  8186.  <item-defn> +
  8187.  ENDMENU
  8188.  
  8189. <identifier> :=
  8190.  {a-z A-Z}+ {a-z A-Z 0-9} *
  8191.  
  8192. <menu-optiion> :=
  8193.  PATH <text>
  8194.  ALIGN { LEFT CENTER }
  8195.  ESCAPE 
  8196.  NOESCAPE 
  8197.  SPACING { 1 2 } 
  8198.  COLUMNS <integer>
  8199.  
  8200. <integer> :=
  8201.  {0-9} +
  8202.  
  8203. <text> :=
  8204.  {
  8205.  "<ascii-string>" 
  8206.  '<ascii-string>' 
  8207.  <no-space-string>
  8208.  }
  8209.  
  8210. <item_defn> :=
  8211.  ITEM [<identifier>] : [<text>]
  8212.  [<item-option>] *
  8213.  
  8214.  <action-code>
  8215.  
  8216. <item-option> :=
  8217.  NEXTITEM
  8218.  {
  8219.  <identifier> 
  8220.  FIRST 
  8221.  LAST 
  8222.  NEXT
  8223.  } 
  8224.  TEXT <text> 
  8225.  HELP <text> 
  8226.  PATH <text> 
  8227.  PROMPT 
  8228.  PAUSE 
  8229.  NOPROMPT 
  8230.  NOPAUSE 
  8231.  NOPRECLEAR 
  8232.  PRECLEAR
  8233.  
  8234. <action-code> :=
  8235.  {
  8236.  ACTION <text> 
  8237.  EXIT 
  8238.  LMENU <identifer> 
  8239.  EMENU <text>
  8240.  }
  8241.  
  8242. <ascii-string> := string containing any
  8243.  ASCII characters, delimited by either
  8244.  single (') or double (") quotes.
  8245.  Line continuation is NOT permitted.
  8246.  
  8247. <no-space-string> := string without
  8248.  quotes, containing no whitespace
  8249.  characters whatsoever. Terminated
  8250.  by first whitespace character.
  8251.  
  8252. Note: the <text> displayed for any one
  8253.  item may be specified either as an
  8254.  <item-option> (the TEXT clause) or
  8255.  immediately following the colon after
  8256.  the ITEM declaration. Exactly ONE
  8257.  of these methods must be used for
  8258.  each item in the menu file.
  8259.  
  8260. Figure 4
  8261.  1: #
  8262.  2: # t.mnu (compiles into t.mnc):
  8263.  3: # Top level menu for CMENU test system. To test this menu,
  8264.  4: # start in a test directory /u/myname/menu, and
  8265.  5: # put the command script "test.sh" into the this directory.
  8266.  6: # Then create a subdirectory named "/u/myname/menu/test"
  8267.  7: # and put the object file for menu t2 (t2.mnc) in it.
  8268.  8: #
  8269.  9:
  8270. 10: menu:
  8271. 11: title "The MAIN Menu" # menu title appears at top
  8272. 12: escape # allow shell escapes
  8273.  
  8274. 13: spacing 2 # double space the entries
  8275. 14:
  8276. 15: item:
  8277. 16: "Run test.sh"
  8278. 17: help 'next item is "zot"'
  8279. 18: action "test.sh" # after the "speed" program runs,
  8280. 19: nextitem zot # item "zot" (below) is highlighted
  8281. 20:
  8282. 21: item:
  8283. 22: "Run External Menu t2"
  8284. 23: path "test" # run menu "t2", found in
  8285. 24: emenu "t2" # subdirectory "test".
  8286. 25:
  8287. 26: item:
  8288. 27: "run a Unix shell"
  8289. 28: action "exec sh" # most efficient with "exec"
  8290. 29:
  8291. 30: item:
  8292. 31: "run shell, no exec." # creates an extra process.
  8293. 32: action sh # (note: quotes not really needed)
  8294. 33:
  8295. 34: item zot:
  8296. 35: 'ITEM "Zot": list the directory (in reverse chron. order)'
  8297. 36: help 'this is a help line for #2, "ZOT"'
  8298. 37: action "ls -t"
  8299. 38: prompt # prompt user before continuing
  8300. 39:
  8301. 40: item
  8302. 41: text "Do a long directly listing (no prompt)"
  8303. 42: help "the next item should ALSO be zot"
  8304. 43: action "l"
  8305. 44: nextitem zot
  8306. 45:
  8307. 46: item
  8308. 47: text "GO TO MENU BAR" # invoke a locally defined menu
  8309. 48: lmenu bar
  8310. 49:
  8311. 50: item
  8312. 51: text "filler" # clone this entry several times
  8313. 52: action "ls" # to see automatic multiple columns
  8314. 53:
  8315. 54: item
  8316. 55: text "filler" # clone this entry several times
  8317. 56: action "ls" # to see automatic multiple columns
  8318. 57: endmenu
  8319. 58:
  8320. 59:
  8321. 60: menu bar:
  8322. 61: title "This is local menu BAR. shell escpaes won't work."
  8323. 62: noescape
  8324. 63: path "/usr/bin"
  8325. 64:
  8326. 65: item
  8327. 66: text "here is the first item of menu BAR"
  8328. 67: action "ls"; prompt # semi OK as separator
  8329. 68:
  8330. 69: item
  8331. 70: text "here is the next item of menu BAR"
  8332. 71: action "ls" prompt # so is a space
  8333.  
  8334. 72:
  8335. 73: item
  8336. 74: text "here is the LAST item of the menu BAR (with help)"
  8337. 75: help "this is help for the LAST item of menu BAR"
  8338. 76: action "ls"
  8339. 77: endmenu
  8340.  
  8341. Figure 5
  8342.  1: #
  8343.  2: # Second test menu: t2.mnu (compiles into t2.mnc)
  8344.  3: #
  8345.  4:
  8346.  5: menu foo:
  8347.  6: title "The MAIN Test Menu for my SECOND menu file"
  8348.  7:
  8349.  8: item fraz:
  8350.  9: text "This is item FRAZ: run the pwd program (next is zot)"
  8351. 10: help "This is a HELP line for item 1 - next item is zot"
  8352. 11: action "pwd"
  8353. 12: prompt
  8354. 13: nextitem zot
  8355. 14:
  8356. 15: item:
  8357. 16: text "This is item #2: run the editor on file foo (next is FRAZ)"
  8358. 17: action "e foo"
  8359. 18: nextitem fraz
  8360. 19:
  8361. 20: item zot:
  8362. 21: text "item ZOT: list the directory (prompt)"
  8363. 22: help "this is a help line for #2"
  8364. 23: action "dir"
  8365. 24: prompt
  8366. 25:
  8367. 26: item text "GO TO MENU #2"
  8368. 27: lmenu bar
  8369. 28:
  8370. 29: endmenu
  8371. 30:
  8372. 31:
  8373. 32: menu bar:
  8374. 33: title "This is the SECOND local menu in the SECOND test file"
  8375. 34:
  8376. 35: item text "here is the first item of the 2nd menu"
  8377. 36: action "dir"
  8378. 37:
  8379. 38: item text "here is the SECOND item of the 2nd menu (with help)"
  8380. 39: help "this is help for that second item of menu #2"
  8381. 40: action "dir"
  8382. 41: endmenu
  8383.  
  8384. Figure 6
  8385. #!/bin/sh
  8386. # Unix script to test return status feature of CMENU
  8387. #
  8388.  
  8389. cat <<END
  8390. This is test.sh running, to test the error status
  8391. feature of the CMENU system.
  8392.  
  8393.  
  8394. If you type 'e' to return an error status, rmenu should
  8395. prompt before returning to the menu. Otherwise, no prompt
  8396. should appear issued:
  8397.  
  8398. Press just return for normal exit,
  8399. or else press 'e' and then the Return key:
  8400. END
  8401.  
  8402. read char
  8403. [ "$char" = e ] && exit 1
  8404. exit 0
  8405.  
  8406. Figure 7
  8407.  .mnc Menu Object File Format:
  8408.  
  8409. <count> (integer count of # of menus in file)
  8410. MENU 1 (MENU structure for 1st Menu)
  8411.  ITEM 1
  8412.  ITEM 2
  8413.  ...
  8414.  ITEM n
  8415. MENU 2 (MENU structure for 2nd Menu)
  8416.  ITEM 1
  8417.  ITEM 2
  8418.  ...
  8419.  ITEM n
  8420.  .
  8421.  .
  8422.  .
  8423. MENU <count> (MENU structure for final Menu)
  8424.  ITEM 1
  8425.  ITEM 2
  8426.  ..
  8427.  ITEM n
  8428.  
  8429. Listing 1
  8430. 1: /***********************************************************
  8431. 2: * Program: CMENU/RMENU Menu Compiler
  8432. 3: * Written by: Leor Zolman, 11/90
  8433. 4: *
  8434. 5: * Module: cmenu.h -- master header file
  8435. 6: *
  8436. 7: * This include file contains definitions common to both
  8437. 8: * the cmenu and rmenu programs.
  8438. 9: ***********************************************************/
  8439. 10:
  8440. 11: #include <stdio.h>
  8441. 12:
  8442. 13: #define VERSION "1.2 (10/7/91)"
  8443. 14:
  8444. 15: /************** System-dependent stuff: *******************/
  8445. 16:
  8446. 17: #if __STDC__ XENIX
  8447. 18: # define Void void
  8448. 19: #else
  8449. 20: # define Void int
  8450. 21: #endif
  8451. 22:
  8452. 23: /******************* Maximum sizes/lengths: ***************/
  8453.  
  8454. 24:
  8455. 25: #define MAX_PATH 30
  8456. 26: #define MAX_MENUS 25
  8457. 27: #define MAX_NEST 3
  8458. 28: #define MAX_TXTWID 60
  8459. 29: #define MAX_ITEMS 36
  8460. 30: #define MAX_CMD 130
  8461. 31: #define MAX_HELP 79
  8462. 32: #define MAX_NAME 20
  8463. 33:
  8464. 34: /****************** Magic constants: *********************/
  8465. 35:
  8466. 36: #define DEFAULT 0
  8467. 37: #define YES 1
  8468. 38: #define NO 2
  8469. 39:
  8470. 40: #define ERROR (-1)
  8471. 41:
  8472. 42: /******************* Nextitem types: **********************/
  8473. 43:
  8474. 44: #define NXT_FIRST 1
  8475. 45: #define NXT_LAST 2
  8476. 46: #define NXT_NEXT 3
  8477. 47: #define NXT_DIRECT 4
  8478. 48:
  8479. 49: /******************* Action types: ************************/
  8480. 50:
  8481. 51: #define ACT_NONE 0
  8482. 52: #define ACT_CMND 1
  8483. 53: #define ACT_LMENU 2
  8484. 54: #define ACT_EMENU 3
  8485. 55: #define ACT_EXIT 4
  8486. 56:
  8487. 57: /******************* typedefs: ****************************/
  8488. 58:
  8489. 59: typedef char BOOL;
  8490. 60:
  8491. 61: typedef struct menu {
  8492. 62: char title[MAX_TXTWID];
  8493. 63: char path[MAX_PATH];
  8494. 64: int nitems;
  8495. 65: char lign;
  8496. 66: int columns;
  8497. 67: int spacing;
  8498. 68: int widest;
  8499. 69: char escape;
  8500. 70: } MENU;
  8501. 71:
  8502. 72: typedef struct item {
  8503. 73: char text[MAX_TXTWID];
  8504. 74: char path[MAX_PATH];
  8505. 75: char action[MAX_CMD];
  8506. 76: char help[MAX_HELP];
  8507. 77: char pre_clear,
  8508. 78: post_clear,
  8509. 79: prompt;
  8510. 80: char acttyp;
  8511. 81: int lmenunum;
  8512. 82: char nextcode;
  8513.  
  8514. 83: int nextitem;
  8515. 84: } ITEM;
  8516.  
  8517. /* End of File */
  8518.  
  8519.  
  8520. Listing 2
  8521. 1: /*************************************************************
  8522. 2: * Program: CMENU Menu Compiler
  8523. 3: * Module: ccmenu.h -- Compiler Module header file
  8524. 4: * Written by: Leor Zolman, 11/90
  8525. 5: *************************************************************/
  8526. 6:
  8527. 7: /******************* Misc. constants ************************/
  8528. 8:
  8529. 9: #define TRUE 1
  8530. 10: #define FALSE 0
  8531. 11: #define OK 0
  8532. 12:
  8533. 13: #define UNDEF_FWD (-1) /* undefined forward reference flag */
  8534. 14:
  8535. 15:
  8536. 16: /******************* extern control ************************/
  8537. 17:
  8538. 18: #ifndef MASTER
  8539. 19: # define Extern extern /* external declarations */
  8540. 20: #else
  8541. 21: # define Extern /* one-time definitions */
  8542. 22: #endif
  8543. 23:
  8544. 24:
  8545. 25: /******************* Type Definitions **********************/
  8546. 26:
  8547. 27: typedef struct {
  8548. 28: char Name[MAX_NAME];
  8549. 29: ITEM Item;
  8550. 30: } IINFO;
  8551. 31:
  8552. 32: typedef struct {
  8553. 33: char Name[MAX_NAME];
  8554. 34: BOOL Processed;
  8555. 35: MENU Menu;
  8556. 36: IINFO *Items[MAX_ITEMS];
  8557. 37: } MINFO;
  8558. 38:
  8559. 39: /******************* Token codes: **************************/
  8560. 40:
  8561. 41: #if __STDC______LINEEND____
  8562. 42:
  8563. 43: enum {
  8564. 44: T_NULL, /* special code */
  8565. 45: T_MENU, T_TITLE, T_PATH,
  8566. 46: T_SPACING, T_COLUMNS, T_ENDMENU,
  8567. 47: T_ITEM, T_TEXT,
  8568. 48: T_ALIGN, T_LEFT, T_CENTER, T_RIGHT,
  8569. 49: T_NEXTITEM, T_FIRST, T_LAST, T_NEXT,
  8570. 50: T_EMENU, T_LMENU, T_ACTION,
  8571. 51: T_HELP,
  8572. 52: T_PROMPT, T_PAUSE, /* synonyms */
  8573.  
  8574. 53: T_NOPROMPT, T_NOPAUSE, /* synonyms */
  8575. 54: T_PRECLEAR, T_NOPRECLEAR, T_POSTCLEAR, T_NOPOSTCLEAR,
  8576. 55: T_EXIT,
  8577. 56: T_ESCAPE, T_NOESCAPE,
  8578. 57: T_STRING, T_VALUE, T_COLON, T_EOF /* special tokens */
  8579. 58: };
  8580. 59:
  8581. 60: #else /* __STDC__ */
  8582. 61:
  8583. 62: #define T_NULL 0 /* special code */
  8584. 63: #define T_MENU 1
  8585. 64: #define T_TITLE 2
  8586. 65: #define T_PATH 3
  8587. 66: #define T_SPACING 4
  8588. 67: #define T_COLUMNS 5
  8589. 68: #define T_ENDMENU 6
  8590. 69: #define T_ITEM 7
  8591. 70: #define T_TEXT 8
  8592. 71: #define T_ALIGN 9
  8593. 72: #define T_LEFT 10
  8594. 73: #define T_CENTER 11
  8595. 74: #define T_RIGHT 12
  8596. 75: #define T_NEXTITEM 13
  8597. 76: #define T_FIRST 14
  8598. 77: #define T_LAST 15
  8599. 78: #define T_NEXT 16
  8600. 79: #define T_EMENU 17
  8601. 80: #define T_LMENU 18
  8602. 81: #define T_ACTION 19
  8603. 82: #define T_HELP 20
  8604. 83: #define T_PROMPT 21 /* synonyms */
  8605. 84: #define T_PAUSE 22
  8606. 85: #define T_NOPROMPT 23 /* synonyms */
  8607. 86: #define T_NOPAUSE 24
  8608. 87: #define T_PRECLEAR 25
  8609. 88: #define T_NOPRECLEAR 26
  8610. 89: #define T_POSTCLEAR 27
  8611. 90: #define T_NOPOSTCLEAR 28
  8612. 91: #define T_EXIT 29
  8613. 92: #define T_ESCAPE 30
  8614. 93: #define T_NOESCAPE 31
  8615. 94: #define T_STRING 32 /* special tokens */
  8616. 95: #define T_VALUE 33
  8617. 96: #define T_COLON 34
  8618. 97: #define T_EOF 35
  8619. 98:
  8620. 99: #endif /*__STDC__*/
  8621. 100:
  8622. 101: /********************** Prototypes: ***********************/
  8623. 102:
  8624. 103: /if __STDC__ XENIX /* ANSI Prototypes: */
  8625. 104:
  8626. 105: int write_file(void);
  8627. 106: int dofile(char *);
  8628. 107:
  8629. 108: void itemcheck(void);
  8630. 109: int gettok();
  8631. 110:
  8632. 111: int error(char*, ...);
  8633.  
  8634. 112: int fatalerr(char *, ...);
  8635. 113:
  8636. 114: MINFO create_menu(char *);
  8637. 115: IINFO *create_item(char *);
  8638. 116: MINFO *find_menu(char *);
  8639. 117: IINFO *find_item(char *);
  8640. 118:
  8641. 119: int do_menu(void);
  8642. 120: int do_title(void);
  8643. 121: int do_path(void);
  8644. 122: int do_spacing(void);
  8645. 123: int do_columns(void);
  8646. 124: int do_item(void);
  8647. 125: int do_endmenu(void);
  8648. 126: int do_align(void);
  8649. 127: int do_text(void);
  8650. 128: int do_text2(void);
  8651. 129: int do_nextitem(void);
  8652. 130: int do_action(void);
  8653. 131: int do_help(void);
  8654. 132: int do_prompt(void);
  8655. 133: int do_clear(void);
  8656. 134: int do_err(void);
  8657. 135: int do_escape();
  8658. 136: int do_opts(void);
  8659. 137:
  8660. 138: #else /* K&R Prototypes: */
  8661. 139:
  8662. 140: int write_file();
  8663. 141: int dofile();
  8664. 142:
  8665. 143: Void itemcheck();
  8666. 144: int gettok();
  8667. 145:
  8668. 146: int error();
  8669. 147: int fatalerr();
  8670. 148:
  8671. 149: MINFO create_menu();
  8672. 150: IINFO *create_item();
  8673. 151: MINFO *find_menu();
  8674. 152: IINFO *find_item();
  8675. 153:
  8676. 154: int do_menu();
  8677. 155: int do_title();
  8678. 156: int do_path();
  8679. 157: int do_spacing();
  8680. 158: int do_columns();
  8681. 159: int do_item();
  8682. 160: int do_endmenu();
  8683. 161: int do_align();
  8684. 162: int do_text();
  8685. 163: int do_text2();
  8686. 164: int do_nextitem();
  8687. 165: int do_action();
  8688. 166: int do_help();
  8689. 167: int do_prompt();
  8690. 168: int do_clear();
  8691. 169: int do_err();
  8692. 170: int do_escape();
  8693.  
  8694. 171: int do_opts();
  8695. 172:
  8696. 173: #endif
  8697. 174:
  8698. 175: #ifdef NEEDSTR
  8699. 176: char *strstr();
  8700. 177: #endif
  8701. 178:
  8702. 179:
  8703. 180: /************ Keyword / function dispatch table ***********/
  8704. 181:
  8705. 182: struct keywd {
  8706. 183: char *keyword;
  8707. 184: int (*t_func)();
  8708. 185: };
  8709. 186:
  8710. 187: extern struct keywd keywords[];
  8711. 188:
  8712. 189: #ifdef MASTER
  8713. 190: struct keywd keywords[] = {
  8714. 191: "(null)", do_err, /* for db only */
  8715. 192: "menu", do_menu,
  8716. 193: "title", do_title,
  8717. 194: "path", do_path,
  8718. 195: "spacing", do_spacing,
  8719. 196: "columns", do_columns,
  8720. 197: "endmenu", do_endmenu,
  8721. 198: "item", do_item,
  8722. 199: "text", do-text,
  8723. 200: "align", do_align,
  8724. 201: "left", do_err,
  8725. 202: "center", do_err,
  8726. 203: "right", do_err,
  8727. 204: "nextitem", do_nextitem,
  8728. 205: "first", do_err,
  8729. 206: "last", do_err,
  8730. 207: "next", do_err,
  8731. 208: "emenu", do_action,
  8732. 209: "lmenu", do_action,
  8733. 210: "action", do_action,
  8734. 211: "help", do_help,
  8735. 212: "prompt", do_opts,
  8736. 213: "pause", do_opts,
  8737. 214: "noprompt", do_opts,
  8738. 215: "nopause", do_opts,
  8739. 216: "preclear", do_opts,
  8740. 217: "nopreclear", do_opts,
  8741. 218: "postclear", do_opts,
  8742. 219: "nopostclear", do_opts,
  8743. 220: "exit", do_action,
  8744. 221: "escape", do_escape,
  8745. 222: "noescape", do_escape,
  8746. 223: "(!string)", do_err, /* for db only */
  8747. 224: "(!value)", do_err, /* for db only */
  8748. 225: "(!colon)", do_err, /* for db only */
  8749. 226: "(!EOF)", do_err /* for db only */
  8750. 227: };
  8751. 228: #endif
  8752. 229:
  8753.  
  8754. 230: #define N_KEYWORDS (sizeof keywords / sizeof (struct keywd))
  8755. 231:
  8756. 232:
  8757. 233: /*************** Other Data structures ********************/
  8758. 234:
  8759. 235: Extern MINFO MInfo[MAX_HENUS], *MIp;
  8760. 236: Extern IINFO *IIp;
  8761. 237:
  8762. 238: Extern MENU *Mp, *CMp; /* General, Current Menu Pointers */
  8763. 239: Extern ITEM *Ip, *CIp; /* General, Current Item Pointers */
  8764. 240:
  8765. 241: Extern struct { /* Item Forward Reference Table */
  8766. 242: char iname[MAX_NAME]; /* Item name */
  8767. 243: int *refp; /* Pointer to reference location */
  8768. 244: int lineno; /* source line number of reference */
  8769. 245: } fwd_refs[MAX_ITEMS];
  8770. 246:
  8771. 247: Extern int n_refs; /* Number of forward references */
  8772. 248:
  8773. 249: /************** Miscellaneous data items ******************/
  8774. 250:
  8775. 251: Extern FILE *fp;
  8776. 252: Extern int token, token2; /* token codes */
  8777. 253: Extern char tparam[MAX_CMD]; /* text parameter */
  8778. 254: Extern int vparam; /* value parameter */
  8779. 255: Extern int lineno; /* current line number */
  8780. 256: Extern int n_menus,
  8781. 257: n_items;
  8782. 258: Extern BOOL in_menu,
  8783. 259: in_item;
  8784. 260: Extern BOOL err_flag,
  8785. 261: fatal;
  8786. 262: Extern int item_num,
  8787. 263: menu_num;
  8788. 264:
  8789. 265: Extern char src_name[MAX_PATH];
  8790. 266: Extern char obj_name[MAX_PATH];
  8791.  
  8792. /* End of File */
  8793.  
  8794.  
  8795. Listing 3
  8796. 1: /*************************************************************
  8797. 2: * Program: CMENU Menu Compiler
  8798. 3: * Module: cmenu1.c
  8799. 4: * Menu Compiler:
  8800. 5: * Main and Utility Functions
  8801. 6: * Written by: Leor Zolman, 7/91
  8802. 7: *************************************************************/
  8803. 8:
  8804. 9: #define MASTER
  8805. 10: #include "cmenu.h"
  8806. 11: #include "ccmenu.h"
  8807. 12:
  8808. 13: #include <string.h>
  8809. 14:
  8810. 15: #if __STDC______LINEEND____
  8811. 16: # include <stdarg.h>
  8812. 17: #else
  8813.  
  8814. 18: # include <varargs.h>
  8815. 19: #endif
  8816. 20:
  8817. 21: int main(argc,argv)
  8818. 22: int argc;
  8819. 23: char **argv;
  8820. 24: {
  8821. 25: register i;
  8822. 26:
  8823. 27: printf("CMENU Menu Compiler v%s\n", VERSION);
  8824. 28: if (argc< 2)
  8825. 29: {
  8826. 30: puts("usage: cmenu <menu-source-file(s)>\n");
  8827. 31: return 0;
  8828. 32: }
  8829. 33:
  8830. 34: for (i = 1; i < argc; i++)
  8831. 35: if (dofile(argv[i]) == ERROR) /* process source files */
  8832. 36: return 1;
  8833. 37: return 0;
  8834. 38: }
  8835. 39:
  8836. 40: /************************************************************
  8837. 41: * dofile():
  8838. 42: * Process a single .mnu source file
  8839. 43: *************************************************************/
  8840. 44:
  8841. 45: int dofile(name)
  8842. 46: char *name;
  8843. 47: {
  8844. 48: register i;
  8845. 49: char *cp;
  8846. 50:
  8847. 51: if ((cp = strstr(name, ".mnu")) 
  8848. 52: (cp = strstr(name, ".MNU")))
  8849. 53: *cp = '\0';
  8850. 54:
  8851. 55: strcpy(src_name, name);
  8852. 56: strcat(src_name, ".mnu");
  8853. 57: strcpy(obj_name, name);
  8854. 58:
  8855. 59: if ((fp = fopen(src_name, "r")) == NULL)
  8856. 60: return fprintf(stderr, "Can't open %s\n", src_name);
  8857. 61:
  8858. 62: n_menus = 0;
  8859. 63: lineno = 1;
  8860. 64: in_menu= FALSE;
  8861. 65: fatal = FALSE;
  8862. 66:
  8863. 67: /* Main processing loop. Read a token and process it,
  8864. 68: * until end of file is reached:
  8865. 69: */
  8866. 70:
  8867. 71: while ((token = gettok(fp)) != T_EOF)
  8868. 72: {
  8869. 73: if (!in_menu && token != T_MENU)
  8870. 74: {
  8871. 75: error("Each menu must begin with the Menu keyword");
  8872. 76: break;
  8873.  
  8874. 77: }
  8875. 78: if ((*keywords[token].t_func)() == ERROR)
  8876. 79: if (fatal) /* If fatal error, exit loop */
  8877. 80: break;
  8878. 81: }
  8879.  
  8880. /* End of File */
  8881.  
  8882.  
  8883.  
  8884.  
  8885.  
  8886.  
  8887.  
  8888.  
  8889.  
  8890.  
  8891.  
  8892.  
  8893.  
  8894.  
  8895.  
  8896.  
  8897.  
  8898.  
  8899.  
  8900.  
  8901.  
  8902.  
  8903.  
  8904.  
  8905.  
  8906.  
  8907.  
  8908.  
  8909.  
  8910.  
  8911.  
  8912.  
  8913.  
  8914.  
  8915.  
  8916.  
  8917.  
  8918.  
  8919.  
  8920.  
  8921.  
  8922.  
  8923.  
  8924.  
  8925.  
  8926.  
  8927.  
  8928.  
  8929.  
  8930.  
  8931.  
  8932.  
  8933.  
  8934.  
  8935.  
  8936.  
  8937. CUG New Releases
  8938.  
  8939.  
  8940. Database Constructing Tools
  8941.  
  8942.  
  8943.  
  8944.  
  8945. Kenji Hino
  8946.  
  8947.  
  8948. Kenji Hino is a member of The C Users' Group technical staff. He holds a
  8949. B.S.C.S. from McPherson College and an undergraduate degree in metallurgy from
  8950. a Japanese university. He enjoys playing drums in a reggae band.
  8951.  
  8952.  
  8953.  
  8954.  
  8955. Updates
  8956.  
  8957.  
  8958.  
  8959.  
  8960. CUG343 C Image Processing System
  8961.  
  8962.  
  8963. Dwayne Phillips (VA) has updated his programs. The update contains a complete
  8964. set of source code including the code that appeared in his "Image Processing"
  8965. articles in CUJ.
  8966.  
  8967.  
  8968. CUG347 TAVL Tree
  8969.  
  8970.  
  8971. Bert C. Hughes (MN) has released ver.2 of his programs and placed them in
  8972. public domain. In this release, he has improved the documentation and
  8973. rewritten code to accommodate the fact that some Standard C compilers do not
  8974. support signed bit fields (Roberto Artigas, Jr has contributed on this
  8975. matter).
  8976.  
  8977.  
  8978. New Releases
  8979.  
  8980.  
  8981.  
  8982.  
  8983. CUG358 cbase
  8984.  
  8985.  
  8986. Lyle Frost (IN) has contributed a shareware version of cbase programs. cbase
  8987. is a complete multiuser C database file management library, providing indexed
  8988. and sequential access on multiple keys. It features a layered architecture and
  8989. comprises four individual libraries:
  8990. chase -- C database library for indexed and sequential access
  8991. lseq -- doubly linked sequential file management library
  8992. btree -- B+-tree file management library
  8993. blkio -- block buffered input/output library
  8994. cbase internally uses lseq for record storage and btree for inverted file
  8995. index storage, which in turn use blkio for file access and buffering. blkio is
  8996. analogous to stdio but based on a file model more appropriate for structured
  8997. files such as used in database software. The lower level libraries can also be
  8998. accessed directly for use independent of cbase. For example, the btree library
  8999. can be used to manipulate B+-trees for purposes other than inverted files, and
  9000. the blkio library to develop new structured file management libraries.
  9001. cbase is written in strict adherence to ANSI C standard while it maintains K&R
  9002. C compatibility. All operating system dependent code is isolated to a small
  9003. portion of the blkio library to make porting to new systems easy. Currently,
  9004. UNIX and DOS systems are supported. For UNIX systems, the programs were tested
  9005. under Interactive UNIX; for DOS systems, Turbo C (v2.0), Turbo C++, and
  9006. Microsoft C v5.1 were used for compiling.
  9007. The distribution disk includes documentation, complete source code for cbase
  9008. (v.1.0.2), and a sample rolodeck card program. Due to the volume of the
  9009. programs, files are archived in ZIP form. Thus, we restrict the distribution
  9010. disk format to MS-DOS.
  9011. Since CUG295 blkio library is a component of this package, we will retire the
  9012. volume.
  9013.  
  9014.  
  9015.  
  9016.  
  9017.  
  9018.  
  9019.  
  9020.  
  9021.  
  9022.  
  9023.  
  9024.  
  9025.  
  9026.  
  9027.  
  9028.  
  9029.  
  9030.  
  9031.  
  9032.  
  9033.  
  9034.  
  9035.  
  9036.  
  9037.  
  9038.  
  9039.  
  9040.  
  9041.  
  9042.  
  9043.  
  9044.  
  9045.  
  9046.  
  9047.  
  9048.  
  9049.  
  9050.  
  9051.  
  9052.  
  9053.  
  9054.  
  9055.  
  9056.  
  9057.  
  9058.  
  9059.  
  9060.  
  9061.  
  9062.  
  9063.  
  9064.  
  9065.  
  9066.  
  9067.  
  9068.  
  9069.  
  9070.  
  9071.  
  9072.  
  9073.  
  9074.  
  9075.  
  9076.  
  9077.  
  9078.  
  9079.  
  9080.  
  9081.  
  9082.  
  9083.  
  9084. Editor's Forum
  9085. Well, it's a new year. I am somewhere in my second year of editing The C Users
  9086. Journal. R&D Publications has moved to new quarters, which I have yet to
  9087. visit. And the C programming language is drifting into its third decade of
  9088. existence.
  9089. I must say that I have mostly enjoyed editing this magazine. Working via
  9090. e-mail from Australia was often a challenge. Some issues I feel I could have
  9091. polished better with a bit more effort. But an occasional kind word from a
  9092. reader goes a long way in this business. The hardest part for me is reading
  9093. the other kind of letters. I find that a little criticism also goes a long
  9094. way. The trick is not to overreact, in either direction.
  9095. R&D seems to be prospering as well, at least from my vantage point as a
  9096. semi-insider. Besides The C Users Journal, they also put out several other
  9097. quality technical publications. (These are not as important as CUJ, of course,
  9098. but you might want to check them out anyway.) And they run The C Users' Group
  9099. and The C Users Bookstore, two valuable services. I expect all of these
  9100. endeavors to become more important as the C community grows.
  9101. In the early 1970s, that community was confined to two floors of one building
  9102. at Bell Labs, Murray Hill, New Jersey. To say that it has grown is the
  9103. grossest of understatements. C is ubiquitous. It is probably the programming
  9104. language in widest use today. We all know that C is not perfect. That's one
  9105. reason why people keep tinkering with it and extending it. But the list of
  9106. successful products written in C keeps growing.
  9107. Not all my reflections on past and future are equally rosy. Certainly, the
  9108. world is wallowing through times of economic uncertainty. Not even the rapidly
  9109. growing computer business is immune to setbacks. And economic hard times have
  9110. a way of depressing everything else.
  9111. Still, it's a new year. I can't help but look on the coming months with
  9112. optimism, at least for CUJ, R&D, and C. I hope you can too.
  9113. P.J. Plauger
  9114. pjp@plauger.uunet
  9115.  
  9116.  
  9117.  
  9118.  
  9119.  
  9120.  
  9121.  
  9122.  
  9123.  
  9124.  
  9125.  
  9126.  
  9127.  
  9128.  
  9129.  
  9130.  
  9131.  
  9132.  
  9133.  
  9134.  
  9135.  
  9136.  
  9137.  
  9138.  
  9139.  
  9140.  
  9141.  
  9142.  
  9143.  
  9144.  
  9145.  
  9146.  
  9147.  
  9148.  
  9149.  
  9150.  
  9151.  
  9152.  
  9153.  
  9154.  
  9155.  
  9156.  
  9157.  
  9158.  
  9159.  
  9160.  
  9161.  
  9162.  
  9163.  
  9164.  
  9165.  
  9166.  
  9167. New Products
  9168.  
  9169.  
  9170. Industry-Related News & Announcements
  9171.  
  9172.  
  9173.  
  9174.  
  9175. Borland Ships Resource Workshop for Windows
  9176.  
  9177.  
  9178. Borland International, Inc. has released The Resource Workshop, a design tool
  9179. for visually creating or customizing Windows resources such as icons, dialogs,
  9180. fonts and bitmap graphics without writing code. A collection of 64 graphical
  9181. icons is included free with each product.
  9182. Resource Workshop is available for IBM personal computers and 100 percent
  9183. compatibles running Windows 3.0 or later. It runs in standard or 386 enhanced
  9184. mode on an 80286 or higher processor and requires a hard disk, 2Mb of RAM,
  9185. plus EGA, Hercules, or VGA graphics and a mouse or other pointing device.
  9186. 2.5Mb of free disk space (3.5Mb if all the files and sample programs are
  9187. loaded) is required.
  9188. Resource Workshop is available direct from Borland for the introductory price
  9189. of $49.95 in the U.S. and Canada. Pricing is in U.S. dollars. Included with
  9190. each Resource Workshop is a free set of more than 64 icons.
  9191. For more information contact Borland International, Inc., 1800 Green Hills
  9192. Road, P.O. Box 660001, Scotts Valley, CA 95067-0001, (800) 331-0877.
  9193.  
  9194.  
  9195. 3-D Graphics For Windows
  9196.  
  9197.  
  9198. WISE Software has released ARENA for Windows 3.0 based on the Z-PHIGS graphics
  9199. standard. ARENA supports most 3-D CAD packages, paint programs, and desktop
  9200. publishing packages. The user can import geometry, objects, and scientific
  9201. data created with 3-D CAD programs or 3-D digitizers. The imported image can
  9202. be rotated, scaled, zoomed, resized, and viewed from any angle.
  9203. For more information contact Wise Software, Seelandstrasse 3, D-2400 Lubeck
  9204. 14, Germany, 0451-3909-142, FAX 0451-3909-499.
  9205.  
  9206.  
  9207. Microware Systems Corporation For Motorola Single Board Computers
  9208.  
  9209.  
  9210. Microware Systems Corp. has released an optimized version of the popular OS-9
  9211. Real-Time Operating System for the Motorola MVME167 single board computer.
  9212. This version of OS-9 for Motorola microprocessor-based products allows
  9213. designers to use the power of the 32-bit M68040 processor, while providing
  9214. complete support for the on-board serial, SCSI, and Ethernet hardware.
  9215. The OS-9/167 Development Pak includes a number of new OS-9 device drivers for
  9216. the next generation I/O peripherals included on the MVME167 board family.
  9217. These include SCSI drivers for the NCR 53C710 controller which support Common
  9218. Command Set flexible and hard disk drives and tape units. Additional drivers
  9219. are included to support the on-board real-time clock and new CD-2401 serial
  9220. I/O controller.
  9221. Full Ethernet support is provided by means of the OS-9 Internet Support
  9222. Package (ISP) and device drivers for the Intel 82597 Ethernet Controller.
  9223. Support for BSD socket-based interprocess communication is also provided.
  9224. OS-9/MVME167 is available in two versions. The OS-9/167 Development Pak
  9225. includes the OS-9/167 Real-Time Operating System module and device drivers as
  9226. well as the full suite of development tools. Cost for the Development Pak is
  9227. $3,000. The OS-9/167 Run- Time Pak provides only the OS-9 Real-Time Operating
  9228. System modules and is intended to provide target system functionality.
  9229. Quantity one pricing for the Run-Time pak is $1,500. Contact Microware for
  9230. multiple-copy licensing information.
  9231. For more information contact Microware, 1900 N.W. 114th St., Des Moines, IA
  9232. 50325, (515) 224-1929, FAX (515) 224-1352.
  9233.  
  9234.  
  9235. High Level Object Management For C++
  9236.  
  9237.  
  9238. Software Ingenuities, Inc. has released Style, a C++ class library designed to
  9239. manage all associations and links between C++ objects. The programmer
  9240. specifies all classes, and the associations between those classes, with a few
  9241. simple declarations. A small, intuitive set of functions is all that is needed
  9242. to build bi-directional links between C++ objects. Style manages the objects
  9243. in a RAM resident database.
  9244. Style provides traversal functions that selectively navigate through the
  9245. database of objects and perform operations on those objects. The operations
  9246. performed by traversal functions can be Style's pre-written functions or can
  9247. be defined by the programmer. Traversal functions separate the task of
  9248. locating objects from the task of performing operations on objects. Because
  9249. the operation is separate from the structure of the application, a programmer
  9250. can easily port traversal functions between applications.
  9251. The suggested retail price of Style starts, at $250; it is available directly
  9252. from Software Ingenuities or through authorized dealers. Style supports
  9253. Borland and Zortech C++. A UNIX version is also available.
  9254. For more information contact Software Ingenuities, Inc., P.O. Box 1586,
  9255. Ballwin, MO 63022, (314) 391-7772, FAX 314-391-0727.
  9256.  
  9257.  
  9258. Mathematica Announces TEMPRA GIF
  9259.  
  9260.  
  9261. Mathematica, Inc. has released TEMPRA GIF v1.03. TEMPRA GIF runs in Windows,
  9262. DOS, or OS/2 Release 2 environments and supports TIFF, TGA, WIN, GIF, PCX, and
  9263. IBM AVC image file formats. TEMPRA GIF supports a complete range of VGA cards,
  9264. as well as more than 295 black-and-white printers. Also built into TEMPRA GIF
  9265. is the ability to capture real-time video through a video digitizer card.
  9266. TEMPRA GIF provides a variety of standard paint tools, photorealistic image
  9267. processing effects, and transformation effects. Paint tools include a
  9268. 256-color palette, patterns, and pens, which can be altered to meet individual
  9269. creative tastes. Geometry functions offer all the shapes and lines necessary
  9270. for detailed artwork, including freehand drawing, arcs, splines, and polygons.
  9271. Other functions, such as antialias, tint, soften, color cycle animation,
  9272. palette adjustment, zoom, and advanced masking, provide a vast array of
  9273. painting combinations. Image canvases can be up to 192Mb in size, or 8K by 8K
  9274. resolution.
  9275. For more information contact Mathematica Inc., 402 S. Kentucky Ave., Lakeland,
  9276. FL 33801, (813) 682-1128, FAX (813) 686-5969.
  9277.  
  9278.  
  9279. COBOL CASE Solution For RISC System/6000
  9280.  
  9281.  
  9282. Netron Inc. has released its CASE product, NETRON/CAP, for IBM's AIX-based
  9283. RISC System/6000 product line.
  9284. In addition to generating native AIX COBOL systems, the initial release of
  9285. this product will generate standard COBOL applications for all environments
  9286. currently supported by NETRON/CAP, including CICS, IMS, DB2, batch, VSAM, MVS,
  9287. VM/CMS, OS/2, MS-DOS and Digital's VAX/VMS.
  9288.  
  9289. NETRON/CAP is a back-end CASE product, based on a unique technology for
  9290. re-using and recycling software components to design and assemble new COBOL
  9291. applications.
  9292. NETRON/CAP for AIX is priced at $10,000 per user. Training and technical
  9293. support/project consulting are extra. For corporate users, a NETRON/CAP
  9294. Starter Kit bundles a five-user development license for AIX with one week of
  9295. training and four weeks of initial implementation support for $65,000. Annual
  9296. product support is 15% of the license price. Volume discounts and special
  9297. vendor pricing are available.
  9298. NETRON/CAP requires AIX v3.1.5 or later and v1 of the AIX VS COBOL
  9299. COMPILER/6000.
  9300. For more information contact Netron, Inc., 99 St. Regis Crescent North,
  9301. Toronto, Canada M3J 1Y9, (416) 636-8333, FAX (416) 636-4847.
  9302.  
  9303.  
  9304. Faxfacts Version 4 Software Supports Brooktrout's Single And Multiple Line Fax
  9305. Boards
  9306.  
  9307.  
  9308. Copia International, developer of the FAXFacts interactive fax retrieval
  9309. system, today announced FAXFacts software support for Brooktrout's 111 and 112
  9310. fax boards.
  9311. Brooktrout, a supplier of high performance voice/fax cards, entered the
  9312. international market four years ago by signing O.E.M. agreements with
  9313. manufacturers or distributors in several foreign countries. By the completion
  9314. of 1991, Brooktrout expects to have approval in 25 countries for their fax
  9315. card use. Brooktrout's unique dual capacity card offers speech playback and
  9316. fax capabilities all on the same card.
  9317. For information via the FAXFacts system call 1-708-924-7465 and request
  9318. document number 8900. For more information contact Copia International, (708)
  9319. 682-8898.
  9320.  
  9321.  
  9322. Cross-C Compiler For Z280 Microprocessor
  9323.  
  9324.  
  9325. Softools has released a new ANSI cross-C compiler for the Z280 microprocessor.
  9326. Control Cross-C for the Z280 comes with a fully integrated MAKE facility,
  9327. built-in assembler, C preprocessor, K&R/ANSI C cross-compiler for DOS
  9328. compatible systems, and includes a stand-alone SASM assembler, linker,
  9329. librarian, five hundred page manual, and a copy of the reference Standard C by
  9330. P.J. Plauger and Jim Brodie.
  9331. Control Cross-C for the Z280 allows the user to compile, link, and execute
  9332. programs up to 16 megabytes in size. It supports banked function calls into
  9333. segments which are not mapped into the logical address space. It also provides
  9334. mapping capability that is handled by the linker automatically, with no source
  9335. program changes. The Control Cross-C package sells for $699.
  9336. For more information contact Softools, Inc., 8770 Manahan Drive, Ellicott
  9337. City, MD 21043, (301) 750-3733, FAX/BBS: (301) 750-2008.
  9338.  
  9339.  
  9340. NABJAooc Now Supports ANSI C And K&R C
  9341.  
  9342.  
  9343. NABJA Software has released a version of its object-oriented development
  9344. environment for C that supports both ANSI C and K&R C compilers.
  9345. NABJAooc is available with compiled libraries for Microsoft C, Microsoft
  9346. QuickC, Borland's Turbo C, Power C 2.0, Watcom C, and Datalight C. Optional
  9347. source code provides full support for any ANSI C or K&R compiler.
  9348. NABJAooc is currently priced at $29.95, with the full source code version only
  9349. $49.95. NABJAooc offers an unconditional 30 day moneyback guarantee. A free
  9350. brochure that describes NABJAooc is available upon request.
  9351. For more information about NABJAooc, contact NABJA Software, P.O. Box 413,
  9352. Girard, PA 16417-0413, (814) 774-3699.
  9353.  
  9354.  
  9355. Clean Coding In Borland C++
  9356.  
  9357.  
  9358. M&T Book's latest offering provides novice to intermediate C and C++
  9359. programmers with a complete guide and tutorial to Borland C++, the new
  9360. object-oriented programming environment for developing DOS and Windows
  9361. applications.
  9362. Clean Coding in Borland C++, by Robert J. Traister, presents clear,
  9363. easy-to-understand explanations of Borland C++'s features and functions plus
  9364. complete coverage of the powerful tools included with it. Expert tips and
  9365. techniques teach programmers how to write clean, efficient Borland C++ code,
  9366. develop Windows applications, debug their programs with Turbo Debugger, design
  9367. Windows icons, dialogs, bitmaps and menu bars with The Whitewater Resource
  9368. Toolkit, and more.
  9369. Clean Coding in Borland C++ is filled with practical, hands-on examples. All
  9370. of the source code is available on disk in PC/MS- DOS format.
  9371. Clean Coding in Borland C++ retails for $26.95, $36.95 with disk. For more
  9372. information contact M&T Books, 501 Galveston Drive, Redwood City, CA
  9373. 94063-4728, (415) 366-3600, FAX (415)366-1685.
  9374.  
  9375.  
  9376. Certification Flash
  9377.  
  9378.  
  9379. 88open has announced the certification of ASCWINDOWS from Summitpointe
  9380. Technologies now available to 88open members and end-users.
  9381. ASCWINDOWS is a windows application development system for character based
  9382. terminals connected to a UNIX-based system. The toolbox allows for creation of
  9383. windowing standards like windows, dialog boxes, pull-down menus, list boxes,
  9384. edit boxes, mode selectors, etc. Applications developed around ASCWINDOWS are
  9385. event driven. ASCWINDOWS comes with a runtime library, a resource compiler,
  9386. and a keyboard compiler. Application resources are defined using resource
  9387. definition language. ASCWINDOWS features default window procedures, that can
  9388. be defined by the user, accelerator keys for faster input processing, user
  9389. configurable virtual keys for application customization, easy
  9390. internationalization of applications, standard user interfaces across
  9391. platforms, and simple naming conventions. ASCWINDOWS applications are terminal
  9392. independent. They are portable to other systems, without modifying the source.
  9393. Certification by 88open is an assurance that software products have been
  9394. thoroughly tested and will run on all MC88000 based systems certified to
  9395. 88open standards. 88open certified RISC systems include: Data General A ViiON,
  9396. Dolphin Triton 88, Harris Night Hawk 4400, Motorola Delta 8000 series, Opus
  9397. 400 & 8000 Personal Mainframes, and Sanyo/Icon 3000, 3380 and 8000.
  9398. For more information on the above product contact 88open Consortium Ltd., 100
  9399. Homeland Ct., Suite 800, San Jose, CA 95112, (408) 436-6600.
  9400.  
  9401.  
  9402. Netron Teams Up With DEC
  9403.  
  9404.  
  9405. Netron, Inc. announced an agreement with Digital Equipment Corporation that
  9406. will make Netron's CASE product, NETRON/CAP, available for the design and
  9407. generation of COBOL applications on VMS systems for the ULTRIX operating
  9408. system. The agreement makes NETRON/CAP one of the first CASE products to
  9409. generate COBOL for ULTRIX, Digital's popular implementation of NIX.
  9410. By offering COBOL development for the ULTRIX operating system, NETRON/CAP
  9411. furher extends Digital's COHESION framework for providing a single application
  9412. development environment across multi-platform/vendor environments. NETRON/CAP
  9413. for ULTRIX also conforms to Digital's NAS (Network Applications Support) open
  9414. systems architecture.
  9415. The agreement calls for a number of key Digital products to be integrated with
  9416. the ULTRIX version of NETRON/CAP. Included will be the integration of the LSE
  9417. (Language Sensitive Editor) and CDD/Repository, support for Rdb as the primary
  9418. database, and use of the VMS Install Utility for software installation.
  9419. Applications will be fully compatible, linkable, and executable on RISC ULTRIX
  9420. systems.
  9421. The minimum configuration necessary to use NETRON/CAP for ULTRIX is a
  9422. DECstation 3100 with 16 MB of main memory, employing ULTRIX v4.2 and using the
  9423. MicroFocus COBOL/2 1.1 compiler.
  9424. NETRON/CAP for the ULTRIX operating system is priced at $10,000 per user.
  9425. Training and technical support/project consulting are extra. For corporate
  9426. users, a NETRON/CAP Starter Kit bundles a five-user development license for
  9427. ULTRIX systems with one week of training and four weeks of initial
  9428. implementation support for $65,000. Annual product support is 15% of the
  9429. license price. Volume discounts and special vendor pricing are available.
  9430. For more information contact Netron, Inc., 99 St. Regis Crescent North,
  9431. Toronto, Canada M3J 1Y9, (416) 636-8333, FAX (416) 636-4847.
  9432.  
  9433.  
  9434.  
  9435. Operating Environment Allows Vendors To Support Industry Standards
  9436.  
  9437.  
  9438. Cadre Technologies Inc. today announced its support for SunSoft's UNIX-based
  9439. operating environment, Solaris v2.0. Cadre will offer its Teamwork line of
  9440. CASE products for UNIX on Solaris. Teamwork will continue to conform to
  9441. industry standard graphical user interfaces by integrating with OPEN LOOK as
  9442. part of Sunsoft's new Solaris environment.
  9443. Solaris is based AT&T's System V Release 4 technology, and consists of three
  9444. functional layers: the operating system layer made up of SunOS v5.0 and ONC,
  9445. the applications layer made up of OpenWindows v3.0 and developer tools, and
  9446. the user layer made up of DeskSet 3.0 and user interfaces.
  9447. Teamwork currently runs on SunOS with OpenWindows and will support the three
  9448. layers of the Solaris environment: SVR4, OpenWindows, and OPEN LOOK/DeskSet.
  9449. The graphical user interface and developer tools that Sun will be offering
  9450. with Solaris will enhance the look and feel of the Teamwork environment.
  9451. For more information contact Cadre Technologies, 222 Richmond Street,
  9452. Providence, RI 02903, (401) 351-CASE, FAX (401) 351-7380.
  9453.  
  9454.  
  9455. WATCOM 32-Bit Development System For DOS And Windows
  9456.  
  9457.  
  9458. WATCOM has released the C8.5/386 Optimizing Compiler and Tools, a 32-bit
  9459. development system for DOS and Windows. Key features include a royalty-free
  9460. 32-bit DOS extender and a true 32-bit Windows GUI and DLL development kit.
  9461. The product supports a range of 80x86 based environments including Windows and
  9462. 32-bit DOS extenders from Rational, Phar Lap and Ergo. C8.5/386 has numerous
  9463. Microsoft language extensions to simplify porting of existing 16-bit code.
  9464. The package includes DOS/4GW, a 32-bit DOS extender developed by Rational
  9465. Systems, Inc.
  9466. WATCOM also has released the 32-bit FORTRAN 77/386 Optimizing Compiler and
  9467. Tools, containing all of the significant new features described here for
  9468. C8.5/386. Also available is WATCOM's 16-bit optimizing compiler packages,
  9469. C8.15 and FORTRAN 77 version 8.5, which include all applicable 16-bit
  9470. enhancements described here for C8.5/386.
  9471. C8.5/386 and FORTRAN 77/386 each have a suggested retail price of $995 and a
  9472. special limited-time introductory price of $795. Upgrades from earlier
  9473. versions are available directly from WATCOM.
  9474. For more information contact WATCOM, 415 Phillip St., Waterloo. Ontario,
  9475. Canada N2L 3X2, (800) 265-4555, Fax (519) 747-4971.
  9476.  
  9477.  
  9478.  
  9479.  
  9480.  
  9481.  
  9482.  
  9483.  
  9484.  
  9485.  
  9486.  
  9487.  
  9488.  
  9489.  
  9490.  
  9491.  
  9492.  
  9493.  
  9494.  
  9495.  
  9496.  
  9497.  
  9498.  
  9499.  
  9500.  
  9501.  
  9502.  
  9503.  
  9504.  
  9505.  
  9506.  
  9507.  
  9508.  
  9509.  
  9510.  
  9511.  
  9512.  
  9513.  
  9514.  
  9515.  
  9516.  
  9517.  
  9518.  
  9519.  
  9520. We Have Mail
  9521. Dear Mr. Plauger,
  9522. I am looking for a software publisher to market a program I have developed. I
  9523. noticed that another person requested similar information (CUJ, September
  9524. 1991, "We Have Mail", p. 132), but you did not provide a method individual
  9525. developers could use to locate marketing agents.
  9526. I have developed a program which is appears to be unique in the DOS/80x86
  9527. world. EP ("Evoked Potential") is a program which integrates data acquisition
  9528. and process control. It was originally developed for the neuroscience
  9529. community but appears to have wide applicability. Before I explain some of its
  9530. features, let me briefly described what Evoked Potentials are.
  9531. Evoked Potentials are EEG signals produced in response to a stimulus. A
  9532. patient is fitted with a cap containing a number of electrodes to detect low
  9533. levels of voltage (potentials) on specific parts of the scalp. Channels of
  9534. analog electrical signals are monitored for activity while the patient is
  9535. subjected to a stimulus. Typical stimuli include visual, somatosensory (ie:
  9536. vibrations) and auditory (sounds). For example, a checker board may flip black
  9537. and white squares on a screen, or a waveform generator produce waveforms at a
  9538. certain frequency to make sounds. One of the most important aspects of
  9539. collecting Evoked Potentials is that the data collected, usually consisting of
  9540. recordings or averaged histograms, is synchronized with the stimulus. This
  9541. synchronization needs to be accurate within a fraction of a millisecond.
  9542. For instance, we may want to collect 100 trials consisting of one second
  9543. samples of 16 channels of analog data at a rate of 1000 samples per second on
  9544. each channel. A real-time display of the averaged histograms must be presented
  9545. during the acquisition. The averaging process may at times reject certain
  9546. trials due to artifacts in the data or other unusual circumstances.
  9547. A researcher begins by first defining an experiment and a protocol. An
  9548. experiment describes the placement of electrodes, the sample rate, and number
  9549. of channels. A protocol describes the behavior of laboratory equipment such as
  9550. waveform generators, other computers, and handcrafted stimulus producing
  9551. equipment. EP uses a simple protocol language which allows the researcher to
  9552. describe the sequence of events and contingent responses the computer should
  9553. perform when various conditions occur (ie., a switch is pressed). A protocol
  9554. can be defined, edited, compiled, and loaded during acquisition. This allows a
  9555. person to start with a simple protocol then enhance and test it incrementally.
  9556. The original purpose of EP was to acquire analog data and synchronously
  9557. control arbitrary laboratory equipment. EP can be configured to acquire up to
  9558. 256 channels of analog data at an aggregate rate of 200,000 samples per
  9559. second. Nonproprietary data acquisition cards and other special equipment may
  9560. be added to the system and accessed in the protocol. Single unit (neuron)
  9561. spike recording capabilities will be available soon. We are also developing a
  9562. clinical, turnkey version for use by technicians here at the Washington
  9563. University Medical School in St. Louis.
  9564. Each of these functions are generally available in one form or another in the
  9565. market place. What is unique is the combination of capabilities in EP.
  9566. Specifically, the ability to monitor and acquire many channels of data at high
  9567. speeds while synchronously controlling external equipment from a MS DOS 80x86
  9568. computer is novel. Further, the speed of creating or changing a protocol
  9569. allows rapid development of complex experiments, an important capability in
  9570. research.
  9571. Surprisingly, people outside of this small community have also expressed
  9572. interest in EP. Due to EP's general design, it turns out to be possible to use
  9573. it to control arbitrary equipment and EP may have application in factories and
  9574. other environments needing to monitor and control complex machines (ie:
  9575. robots). Several university laboratories on the east coast are considering
  9576. incorporating it into their work. A group studying Positron Emission
  9577. Tomography (PET) here at Washington University are considering its use in
  9578. counting gamma ray emissions from bank pairs of a PET machine.
  9579. I am a computer scientist, not a marketing person. I admit my ignorance in
  9580. that area but I have a feeling that what I have may be useful in a broader
  9581. range of applications. How do I go about finding people interested in
  9582. marketing this program? It seems to be a relevant question to any programmer
  9583. who feels they have produced a useful program. Is it consistent with the
  9584. philosophy of the C Users Journal to raise such questions in these pages?
  9585. Sincerely,
  9586. Sheldon Hoffman
  9587. 917 Alanson Dr.
  9588. St. Louis, MO. 63132
  9589. It's hard to give general advice about how to market computer software. More
  9590. and more companies are learning the advantages, and logistics, of distributing
  9591. software developed by others. These companies range in size from Microsoft
  9592. down to very specialized niche marketers working a limited geographical area.
  9593. In your case, I'd say you have to do a lot of educating of potential customers
  9594. -- at least outside the very limited application area you now inhabit. Your
  9595. best bet may be to gain experience with your current crop of potential
  9596. customers. My experience is that word of mouth does at least half the job of
  9597. technical selling. So long as you don't sign away all rights to your product
  9598. early on, you have a good chance of eventually connecting up with a marketeer
  9599. you can trust.
  9600. Meanwhile, all I can do is give you some exposure here and wish you good luck.
  9601. -- pjp
  9602. Dear C Users Journal,
  9603. I am writing in response to Belinda Aboshanab's letter, published in The C
  9604. Users Journal, September 1991. In her letter she complains about Microsoft
  9605. OuickC's requirement of additional royalty payments due to Bitstream for any
  9606. commercial distribution of their fonts. She asked if Borland had the same
  9607. policy.
  9608. I am an avid fan of Borland's products. I currently own Borland's Turbo C++
  9609. 2.0 and Professional and Turbo Pascal for Windows. I am desperately trying to
  9610. learn the Windows 3.0 API well enough to write some commercial grade software
  9611. which I intend to distribute via shareware. I am extremely pleased with
  9612. Borland's products and their technical support. I am approximately halfway
  9613. through Charles Petzold's Programming Windows Second Edition. I have yet to
  9614. encounter a programming example in the book that would not compile and run
  9615. properly using Borland's C++ compiler.
  9616. As for Ms. Aboshanab's question about royalty payments, I quote from Borland's
  9617. No-nonsense License Statement: "Programs that you write and compile using
  9618. Borland's language compilers, and that contain any portion of Borland code,
  9619. may be used, given away or sold without additional license or fees, as long as
  9620. all copies of these programs bear a valid copyright notice. By "copyright
  9621. notice," we mean either your own copyright notice or the copyright notice
  9622. which appears on the original diskette label on your Borland language compiler
  9623. product. Borland's language compilers may include various support files that
  9624. contain encoded hardware and font information used by the runtime library. You
  9625. may use these proprietary Borland files with the programs you create with
  9626. Borland language compilers for your own personal use. In addition, if the
  9627. programs you write and compile using a Borland language compiler make use of
  9628. these support files, you may distribute these support files in combination
  9629. with these programs, provided that you do not use, give away, or sell the
  9630. support files separately, and all copies of your programs bear a valid
  9631. copyright notice."
  9632. In my opinion Borland is the leader in reasonable license agreements. They
  9633. even grant you permission to use their product on more than one machine,
  9634. provided there can never be more than one copy in use at the same time.
  9635. Borland also frequently offers special deals to computer magazine subscribers.
  9636. Their Turbo C++ 2.0 Professional Compiler package includes a full
  9637. ANSI-compatible C compiler, a C++ compiler which conforms to AT&T's 2.0
  9638. specification, the Turbo Assembler, two Turbo Debuggers (one for DOS and one
  9639. for Windows), and the Whitewater Resource Toolkit for creating Windows icons,
  9640. bitmaps, etc. All this for a list price of $495. I have seen it offered for as
  9641. low as $149. Since Ms. Aboshanab is a registered owner of Microsoft's QuickC,
  9642. she may qualify for a discount on the purchase of Turbo C++ 2.0. It would
  9643. certainly be worth her time to call Borland and ask about it.
  9644. Now that I have thoroughly plugged Borland and Microsoft Windows, how about
  9645. some articles on Windows programming techniques? Thank you for an excellent
  9646. magazine.
  9647. Leon G. Rollison
  9648. 116 Towne Creek Trail
  9649. Anderson, SC 29621
  9650. Thanks for the clarification. We are always on the lookout for good articles
  9651. on Windows programming. -- pip
  9652. You can also try the Windows/DOS Developer's Journal. For information, call
  9653. Customer Service.-- rlw
  9654. Hello (via e-mail),
  9655. As a reader of The C Users Journal I have been looking forward to each new
  9656. article in your series on the ANSI C standard (now covering the headers). I
  9657. recall you commenting that you didn't see much use for the new syntax of the
  9658. sizeof operator which allows you to determine the size of an expression. I
  9659. wonder if you have any other solution to the following problem relating to
  9660. that feature?
  9661. Given:
  9662. typedef struct dbe {
  9663. struct dbe *next;
  9664. char name [8] ;
  9665. int sequence ;
  9666. char * comment ;
  9667. } DBEntry_t ;
  9668. and I want to code a comparison between a given string and the name field of a
  9669. specific structure. Right now it looks something like this:
  9670. DBEntry_t * current ;
  9671. char * some_string ;
  9672.  
  9673. strncmp( some_string, current->name,
  9674. sizeof( current->name ) )
  9675. I can't think of any other way to get the size of the name field in the
  9676. DBEntry_t structure. I'm not terribly pleased with this code because I have to
  9677. declare a structure or structure pointer to be able to specify the field. This
  9678. limits my ability to create a macro to provide this value. I'm also worried
  9679. that the expression current->name would have type char * on some C compilers.
  9680. But it seems like ANSI covered that.
  9681. Do you know of any other way to specify a field of a structure to the sizeof
  9682. operator without using the expression syntax? (Something like DBEntry_t.name?)
  9683. Thank you for your attention,
  9684. Sincerely,
  9685. Philip D. Pokorny
  9686. philip@cel.cummins.com
  9687. Your code is fine -- I do that sort of thing all the time. It's a minor
  9688. nuisance that you have to name an explicit pointer to a structure when you
  9689. want to inquire about the size of one of its members, but C has worse
  9690. limitations than that.
  9691. What I have branded as useless in the past is taking the sizeof an expression
  9692. that yields only an rvalue. The expression you wrote is an array lvalue.
  9693. That's just the sort of thing that sizeof should work on. Don't be distracted
  9694. by the fact that an array lvalue almost invariably changes to a char * rvalue.
  9695. For the sizeof operator and the address-of operator, the arrayness sticks
  9696. around. -- pjp
  9697. Dear Mr. Plauger,
  9698. In the September 1991 issue of CUJ, you give your e-mail address as
  9699. pjp@wsa.oz. As I understand things, that's not quite correct -- your address
  9700. should be given as pjp@wsa.oz.au. The form you gave was appropriate for the
  9701. old ACSnet mail system, which was internal to Australia (although a lot of
  9702. gateway sites knew how to reach it). The form with .au is a valid Internet
  9703. address, with the .AU indicating that you are in Australia.
  9704. In any case, I hope you are enjoying your stay in Australia. I'm an American
  9705. myself, so I can appreciate the rather subtle sort of "culture shock" you've
  9706. probably been experiencing.
  9707. Sincerely,
  9708. Eric Zurcher
  9709.  
  9710. CSIRO Division of Entomology
  9711. Canberra ACT 2601
  9712. E-mail: ericz@ento.csiro.au
  9713. I welcome corrections to my rendition of e-mail addresses. I am too senile
  9714. ever to understand the intricacies of intertwined networks that snake around
  9715. the world. I'm only grateful that they work sometimes. -- pjp
  9716. Dear Sir:
  9717. Three short answer questions for you.
  9718. 1) How can you pass data to a spawned process, and back again, either in C or
  9719. 8086 assembler? I need to pass up to about 30 data items.
  9720. 2) Is there a magazine, with focus and coverage similar to CUJ, for 8086
  9721. assembler?
  9722. 3) How would you define the term "side effect"? I've seen the term frequently
  9723. used, have some vague understanding of what it means (I think), but have never
  9724. seen a formal definition.
  9725. I like you magazine very much. I find it always interesting, if not
  9726. immediately useful.
  9727. Sincerely,
  9728. John Beach
  9729. 1025 Medburst Rd.
  9730. Columbus, OH 43220
  9731. I assume you're talking about MS-DOS. You could probably set aside an area in
  9732. the parent process large enough to hold the data to be passed. Getting the
  9733. pointer to the spawned process is not easy, however. To pass it as part of the
  9734. command line, you really need to encode the pointer as hexadecimal text, or
  9735. some such. If the overhead is tolerable, you're best off opening a temp file
  9736. in the parent and having the child write data to it.
  9737. Other magazines besides CUJ show more assembly language than we do. Check out
  9738. our sister publication, Windows/DOS Developer's Journal.
  9739. I define side effect as a change in the state of a file or the value stored in
  9740. a data object. I think that's about as good a definition as any.
  9741. Glad you like the magazine. -- pjp
  9742.  
  9743.  
  9744.  
  9745.  
  9746.  
  9747.  
  9748.  
  9749.  
  9750.  
  9751.  
  9752.  
  9753.  
  9754.  
  9755.  
  9756.  
  9757.  
  9758.  
  9759.  
  9760.  
  9761.  
  9762.  
  9763.  
  9764.  
  9765.  
  9766.  
  9767.  
  9768.  
  9769.  
  9770.  
  9771.  
  9772.  
  9773.  
  9774.  
  9775.  
  9776.  
  9777.  
  9778.  
  9779.  
  9780.  
  9781.  
  9782.  
  9783.  
  9784.  
  9785.  
  9786.  
  9787. Nearest Neighbor Algorithm For Color Matching
  9788.  
  9789.  
  9790. Geoffrey Probert
  9791.  
  9792.  
  9793. Geoffrey Probert has been programming in C for nine years. He currently works
  9794. at Aztek Engineering. He can be reached at (303) 466-9710 or (303) 786-9100
  9795. ext. 144.
  9796.  
  9797.  
  9798. A problem generally referred to as a nearest neighbor, or closest neighbor,
  9799. problem begins with a fixed set of points whose locations are all known. When
  9800. given a new point, you must find the nearest point in the original population
  9801. to that new point. Most beginning programmer text books deal with the
  9802. one-dimensional version of the nearest neighbor problem. They solve the
  9803. problem by sorting the elements and then doing a binary search. Text books do
  9804. not usually deal with the multi-dimensional version of this problem. This
  9805. article will discuss an approach guaranteed to find the nearest neighbor to a
  9806. point in a multi-dimensional space.
  9807.  
  9808.  
  9809. The Problem
  9810.  
  9811.  
  9812. In my application, I needed to display an image on a VGA screen (640 pixels by
  9813. 480 lines with 256 colors) that was originally 768 pixels by 512 lines with
  9814. 256 colors. Since the palette for the original image already existed, it
  9815. seemed best to keep that same palette. For each of the pixels in the VGA
  9816. image, I computed the red, green, and blue values using bilinear interpolation
  9817. as shown in Figure 1 (see "Resampling Methods for Image Manipulation" by
  9818. Girish T. Hagan in the August 1991 issue of C Users Journal). After computing
  9819. the red, green, and blue values, I needed to select the color from the palette
  9820. that was the closest match to the computed values.
  9821. The first approach to this problem is the brute force method. This involves
  9822. computing the distance from each of the desired values to all of the available
  9823. neighbors. If I am generating an image of 640 pixels by 480 lines with a
  9824. palette of 256 colors, the distance must be calculated 78.6 million times.
  9825. This takes a considerable amount of time even on a fast CPU and inspires the
  9826. search for a faster algorithm. I approached this problem with an algorithm
  9827. that limits the number of distance calculations and comparisons.
  9828. The algorithm contains two parts. The first part initializes data and
  9829. structures. It does this once before any searching for a nearest neighbor. The
  9830. second part actually searches for the nearest neighbor for each new pixel
  9831. being generated.
  9832.  
  9833.  
  9834. Two-Dimensional Problem
  9835.  
  9836.  
  9837. To visualize the algorithm more easily, look at the two-dimensional problem
  9838. first. In the preparation phase, I proceed just as in a one-dimensional
  9839. problem. I sort all of the neighbors using just one of the dimensions, say X.
  9840. Since I am sorting the neighbors, I must also bring along the values for the
  9841. rest of the dimensions (Y in the two-dimensional case).
  9842. The search phase is a two part process. First I must find the nearest neighbor
  9843. using just the single dimension I originally sorted on, in this case X. This
  9844. neighbor becomes the initial estimate. The program computes and stores the
  9845. distance from this initial estimate to the real point as the shortest
  9846. distance. Next, I begin to search to the left and to the right, in X, of the
  9847. initial estimate. At each of these points the program computes the distance to
  9848. the real point. If it is less than the shortest distance so far, then it
  9849. becomes the new shortest distance. The trick is to end the search before
  9850. searching all of the points. To do this, the program also computes the
  9851. distance in the sorted dimension only (X). Once this distance is greater than
  9852. the shortest distance for both the left and right sides, I know that no more
  9853. points need to be looked at. The nearest neighbor corresponds to the point
  9854. that had the shortest distance to the desired point.
  9855.  
  9856.  
  9857. Two-Dimensional Example
  9858.  
  9859.  
  9860. The example in Figure 2 illustrates this concept. There are nine neighbors,
  9861. each indicated by a dot. An X indicates the desired location at (6,2). Assume
  9862. the sort along the X dimension has been done.
  9863. The sort along the X dimension results in an initial estimate at the point
  9864. (6,7). The distance to this point is 5, initially the shortest distance.
  9865. The first point to be examined would be to the left of the initial estimate,
  9866. at (5,5). The X distance is 1, which is less than the shortest distance, 5.
  9867. Therefore I compute the distance, sqrt(10), to this point. This is less than
  9868. the shortest distance and therefore replaces the shortest distance.
  9869. The next point to be examined is to the right of the initial estimate and is
  9870. at (7,4). The X distance is 1, which is less than the shortest distance,
  9871. sqrt(10). Therefore I compute the distance, sqrt(5), to this point. This is
  9872. less than the shortest distance and therefore replaces the shortest distance.
  9873. The next point to be examined is to the left of the initial estimate and is at
  9874. (4,5). The X distance is 2, which is less than the shortest distance, sqrt(5).
  9875. Therefore I compute the distance, sqrt(13), to this point. This is greater
  9876. than the shortest distance and therefore does not replace the shortest
  9877. distance.
  9878. The next point to be examined is to the right of the initial estimate and is
  9879. at (8,5). The X distance is 2, which is less than the shortest distance,
  9880. sqrt(5). Therefore I compute the distance, sqrt(13), to this point. This is
  9881. greater than the shortest distance and therefore does not replace the shortest
  9882. distance.
  9883. The next point to be examined is to the left of the initial estimate and is at
  9884. (3,4). The X distance is 3, which is greater than the shortest distance,
  9885. sqrt(5). Therefore I can quit searching to the left of the initial estimate.
  9886. The next point to be examined is to the right of the initial estimate and is
  9887. at (9,4). The X distance is 3, which is greater than the shortest distance,
  9888. sqrt(5). Therefore I can quit searching to the right of the initial estimate.
  9889. I have finished searching to both the left and the right of the initial
  9890. estimate, therefore the nearest neighbor has been found at (7,4) with a
  9891. distance of sqrt(5).
  9892.  
  9893.  
  9894. Color Matching, Three Dimensions
  9895.  
  9896.  
  9897. In the application that I was working on, an image was being generated for
  9898. display at 640 pixels by 480 lines by 256 colors. The palette was already
  9899. present with the original image. The individual color values ranged from 0 to
  9900. 255. These would later be scaled for the VGA palette which ranges from 0 to
  9901. 63. The size of my arrays and structures were designed to handle the larger
  9902. range of 0 to 255. I used the "red" dimension for my sort.
  9903. Listing 1 declares the global variables needed by the routines. In order to
  9904. generate new pixels by interpolating from the original image, a copy of the
  9905. original palette (palo) must be kept to have access to the original colors.
  9906. The copy of the palette is named pals. It carries along the index of the
  9907. original palette entry in num. This allows the original palette in the
  9908. original order to be used for the new image. To speed things up, the square of
  9909. the distance between points was computed rather than the actual distance.
  9910. To speed up the acquisition of the initial estimate, another array was
  9911. initialized during the preparation phase. Since there were only 256 possibly
  9912. values in the sorted dimension, I generated an array named redindex that would
  9913. immediately index into the sorted palette for the initial estimate.
  9914. Listing 2 details the initialization phase. The routine sort_color makes a
  9915. copy of the original palette and uses qsort to sort on the red dimension. The
  9916. routine color_comp is the comparison routine for qsort. After sorting pals,
  9917. the program generates the indexing array redindex. To do this, it uses two
  9918. indices, one to step through redindex, and the other to step through pals. It
  9919. sets each value in redindex to the closest value there is in pals.
  9920. Listing 3 does the actual search for the nearest neighbor. It finds the
  9921. initial estimate in redindex and computes the shortest distance for it. My
  9922. search looks at one possibility on the left and then at a possibility on the
  9923. right. The program computes the "red distance" followed by the real distance,
  9924. if applicable. Listing 3 also provides tests to make sure the search remains
  9925. within the bounds of pals. The dist routine computes the square of the
  9926. distance between a palette entry and the desired red, green, and blue values.
  9927.  
  9928.  
  9929. Summary
  9930.  
  9931.  
  9932. This algorithm is over 45 times faster than the brute force method would be in
  9933. my application. Stopping the search before all possible values were examined
  9934. contributed the most to this increase in speed. Using redindex to get a quick
  9935. initial estimate also contributed significantly.
  9936. This algorithm should be applicable to any multidimensional nearest neighbor
  9937. problem. One word of caution: if your data is clumped in one dimension and
  9938. spread out in another, you should sort along the spread out dimension. This
  9939. will speed up the search algorithm.
  9940. Figure 1
  9941. Figure 2
  9942.  
  9943.  
  9944. Listing 1
  9945. #include <stdio.h>
  9946. #include <stdlib.h>
  9947.  
  9948. #define PAL_LEN 256
  9949.  
  9950. typedef struct
  9951. {
  9952. int red;
  9953. int grn;
  9954. int blu;
  9955. int num;
  9956. } pal;
  9957.  
  9958. pal palo[ PAL_LEN ]; /* original palette */
  9959. pal pals[ PAL_LEN ]; /* sorted palette */
  9960.  
  9961. /* index for the sorted palette */
  9962. int redindex[ PAL_LEN ];
  9963.  
  9964. /* prototypes */
  9965.  
  9966. int closest( int red, int green, int blue );
  9967. long dist( int red, int green, int blue, int num );
  9968. void sort_color();
  9969. int color_comp( const void *a, const void *b );
  9970.  
  9971. /* End of File */
  9972.  
  9973.  
  9974. Listing 2
  9975. /*begin************************************************
  9976. * Program : sort_color
  9977. * Descript. : Sorts the palette by the red color only
  9978. * Also builds the index array
  9979. * for fast lookup.
  9980. *end**************************************************/
  9981.  
  9982. void sort_color()
  9983. {
  9984. int i;
  9985. int j;
  9986.  
  9987. /* Make a copy of the original palette */
  9988. for ( i=0; i<PAL_LEN; i++ )
  9989. {
  9990. pals[ i ].num = i;
  9991. pals[ i ].red = palo[ i ].red;
  9992. pals[ i ].grn = palo[ i ].grn;
  9993. pals[ i ].blu = palo[ i ].blu;
  9994. }
  9995.  
  9996. /* Sort the copy of the palette */
  9997. qsort( (void *)pals, PAL_LEN, sizeof(pal), color_comp);
  9998.  
  9999. /* build the quick index */
  10000. /* so we don't have to do a */
  10001. /* binary search every time */
  10002.  
  10003.  
  10004. for ( i=0, j=0; i<PAL_LEN; i++ )
  10005. {
  10006. while( pals[ j ].red < i )
  10007. if ( ++j >= PAL_LEN )
  10008. {
  10009. j = PAL_LEN - 1;
  10010. break;
  10011. }
  10012. redindex[ i ] = j;
  10013. }
  10014. }
  10015.  
  10016.  
  10017. /*begin*************************************************
  10018. * Program : color_comp
  10019. * Descript. : Compares two colors (red) in a palette.
  10020. *end***************************************************/
  10021.  
  10022. int color_comp( const void *a, const void *b )
  10023. {
  10024. pal *aa;
  10025. pal *bb;
  10026.  
  10027. aa = (pal *)a;
  10028. bb = (pal *)b;
  10029. return( aa->red - bb->red );
  10030. }
  10031.  
  10032. /* End of File */
  10033.  
  10034.  
  10035. Listing 3
  10036. /*begin************************************************
  10037. * Program : closest
  10038. * Descript. : Returns the number of the closest color
  10039. * from the palette, given the desired red
  10040. * green, and blue. Find the closest point
  10041. * by using the red index arrey. Look at
  10042. * points on both sides of the closest
  10043. * point. While searching, if a point is
  10044. * closer then it is the shortest distance
  10045. * Once you are examining points that are
  10046. * farther away in just the one dimension
  10047. * (red) then you are done.
  10048. *end**************************************************/
  10049.  
  10050. int closest( int red, int green, int blue )
  10051. {
  10052. long least;
  10053. long dif;
  10054. long sum;
  10055. int index;
  10056. int left;
  10057. int right;
  10058.  
  10059. index = redindex[ red ];
  10060. least = dist( red, green, blue, index );
  10061. left = index;
  10062.  
  10063. right = index;
  10064.  
  10065. while( ( left >= 0 ) (right < PAL_LEN ) )
  10066. {
  10067. if ( -left >= 0 )
  10068. {
  10069. /* if red dist. alone is greater, then quit */
  10070. dif = red - pals[ left ].red;
  10071. if ( ( dif * dif ) > least )
  10072. left = -1;
  10073. else
  10074. {
  10075. sum = dist( red, green, blue, left );
  10076. if ( sum < least )
  10077. {
  10078. least = sum;
  10079. index = left;
  10080. }
  10081. }
  10082. }
  10083. if ( ++right < PAL_LEN )
  10084. {
  10085. /* if red dist. alone is greater, then quit */
  10086. dif = red - pals[ right ].red;
  10087. if ( ( dif * dif ) > least )
  10088. right = PAL_LEN;
  10089. else
  10090. {
  10091. sum = dist( red, green, blue, right );
  10092. if ( sum < least )
  10093. {
  10094. least = sum;
  10095. index = right;
  10096. }
  10097. }
  10098. }
  10099. }
  10100. return( pals[ index ].num );
  10101. }
  10102.  
  10103.  
  10104. /*begin*************************************************
  10105. * Program : dist
  10106. * Descript. : Color distance (squared)
  10107. *end**************************************************/
  10108.  
  10109. long dist( int red, int green, int blue, int num )
  10110. {
  10111. long dif;
  10112. long sum;
  10113. pal *palt;
  10114.  
  10115. palt = &pals[ num ];
  10116. dif = red - palt->red;
  10117. sum = dif * dif;
  10118. dif = green - palt->grn;
  10119. sum += dif * dif;
  10120. dif = blue - palt->blu;
  10121. sum += dif * dif;
  10122.  
  10123. return( sum );
  10124. }
  10125. /* End of File */
  10126.  
  10127.  
  10128.  
  10129.  
  10130.  
  10131.  
  10132.  
  10133.  
  10134.  
  10135.  
  10136.  
  10137.  
  10138.  
  10139.  
  10140.  
  10141.  
  10142.  
  10143.  
  10144.  
  10145.  
  10146.  
  10147.  
  10148.  
  10149.  
  10150.  
  10151.  
  10152.  
  10153.  
  10154.  
  10155.  
  10156.  
  10157.  
  10158.  
  10159.  
  10160.  
  10161.  
  10162.  
  10163.  
  10164.  
  10165.  
  10166.  
  10167.  
  10168.  
  10169.  
  10170.  
  10171.  
  10172.  
  10173.  
  10174.  
  10175.  
  10176.  
  10177.  
  10178.  
  10179.  
  10180.  
  10181.  
  10182.  
  10183.  
  10184.  
  10185.  
  10186. Hashing: From Good To Perfect
  10187.  
  10188.  
  10189. Ron Burk
  10190.  
  10191.  
  10192. Ron Burk has a B.S.E.E. from the University of Kansas and has been a
  10193. programmer for the past 10 years. You may contact him at Burk Labs, P.O. Box
  10194. 3082, Redmond, WA 98073-3082. CIS: 70302, 2566.
  10195.  
  10196.  
  10197. Hashing is one of the most elegant responses in computer software to the
  10198. limitations of computer hardware. You may not have seen hashing used anywhere
  10199. but in limited database applications, but the basic algorithm has found wide
  10200. use. This article covers some of the ways the hashing algorithm has been
  10201. extended, and its application to a variety of problems. Some useful code
  10202. fragments may aid your own implementation of hashing. First, however, let's
  10203. review basic hashing.
  10204.  
  10205.  
  10206. The Basics
  10207.  
  10208.  
  10209. Probably the most common use of hashing is to organize database files. Suppose
  10210. you are writing software that runs cash machines for a bank with 50,000
  10211. customers. When a customer inserts their card into the cash machine, your
  10212. software must quickly locate that customer's records (such as the account
  10213. balance) using the 16-digit number on their card. In database terminology, the
  10214. card number is the "key" to the customer database.
  10215. Clearly, a sequential search of 50,000 records will require too much disk I/O.
  10216. An indexed file scheme (such as a B-Tree) could cut the search to just two or
  10217. three disk reads, but even that may be prohibitive; you must provide fast
  10218. responses to many customer requests on inexpensive hardware.
  10219. Hashing is a simple, efficient solution to this problem. First, create the
  10220. card database as a file containing 2N empty, fixed-length records, where N is
  10221. the total number of records you expect to store. Second, store each customer
  10222. entry at the record position obtained by feeding that customer's card number
  10223. through a hashing function (a mathematical function that transforms the key to
  10224. an integer value in the range 0 to 2N-1). Here is an example of a simple
  10225. hashing function that maps 16-digit card numbers into a record number between
  10226. 0 and 99,999:
  10227. long hash(char *cardnum) {
  10228. long h = 0;
  10229. whi1e (*cardnum)
  10230. h = (h<<4) ^ *cardnum++;
  10231. return h%100000;
  10232. }
  10233. The hashing function uses the record key to calculate a location for the
  10234. record, whereas an indexed scheme like a B-tree uses tables to store and
  10235. retrieve the correct record position. The advantage of hashing is speed; there
  10236. are some disadvantages, however.
  10237. The most obvious disadvantage of hashing is the problem of collisions. For
  10238. example, the hashing function given above maps card number 1234567812345678
  10239. and card number 1202120038744598 to the same record position: 32808. A simple
  10240. solution to collisions is to store the record at the next empty position. In
  10241. general, you must store the record key as part of the record to determine
  10242. whether the hashing function has indexed the desired record or the subsequent
  10243. positions must be searched due to a collision.
  10244. Another shortcoming of hashing is the requirement for fixed-size tables. While
  10245. B-trees are designed to grow and shrink gracefully, adapting to record
  10246. insertions and deletions, the basic hashing scheme requires an I/O-intensive
  10247. reorganization to convert tables to larger or smaller sizes. Worse, extra
  10248. space is usually allocated for hash tables to reduce the number of collisions.
  10249. That is why the example used a hash table twice as large as the expected
  10250. number of entries.
  10251. Still another problem with hashing is that the records are scattered
  10252. arbitrarily in a disk file. Suppose you have a large database of customers in
  10253. which the primary key is the customer name. If the file is organized as a
  10254. B-tree, then producing a listing sorted by customer name requires relatively
  10255. little disk I/O, since the records themselves are maintained sorted by key. If
  10256. the same database is kept as a hashed file, then accessing the customers in
  10257. sorted order may require as many disk reads as there are customer records.
  10258. Some interesting algorithms have evolved from the basic hashing algorithm;
  10259. some attempt to address shortcomings, while others simply put various
  10260. disadvantages to good use.
  10261.  
  10262.  
  10263. Extendible Hashing
  10264.  
  10265.  
  10266. For some time, there was a dichotomy between hashed database organizations and
  10267. indexed database organizations. You could get fast record access with a hashed
  10268. file or the ability to grow and shrink gracefully with a B-tree, but you
  10269. couldn't get both. In the past decade, however, several hashing techniques for
  10270. expandable files have been devised.
  10271. Extendible hashing is an algorithm that provides the speed of a hashed file
  10272. and the extensibility of a B-tree. Extendible hashing requires a hash function
  10273. and a table of pointers to disk pages. A disk page is simply a fixed-size
  10274. chunk of disk (say, 4K bytes long) which is the basic unit of disk I/O for the
  10275. database. A particular page may contain many database records. In general, the
  10276. cost of disk I/O is high enough that database design focuses on locating the
  10277. correct page in as few disk reads as possible and ignores the cost of locating
  10278. the correct record within that page.
  10279. Extendible hashing views the hashed key as an index into the table of page
  10280. pointers. The size of the page table is required to be a power of two; only
  10281. enough bits to address the full page table are used from the hashed key as an
  10282. index.
  10283. The algorithm is easier to understand with an example. Figure 1 shows an
  10284. extendible hash database which contains a few records. The page table has two
  10285. entries and both point to empty database pages. To insert a record, hash the
  10286. record key and use the most significant bit of the hash as an index into the
  10287. page table. The result points to the page where the record should be inserted.
  10288. So far this is simple. All new records will be inserted into one of the two
  10289. database pages, based on the first bit of the hashed record key. However, when
  10290. one of the pages becomes too full to add another record, that page must split
  10291. and things get interesting. Figure 2 shows what might happen to the database
  10292. in Figure 1 after one more record insertion.
  10293. When a page in the database overflows, there are too many keys with identical
  10294. values in their first N bits, where 2N is the current page table size. To
  10295. handle the overflow, you double the size of the page table and divide the
  10296. records in the too-full page between that page and its new "buddy" page -- the
  10297. page whose index is the same except for the last bit. For example, the buddy
  10298. of page 0110 is page 0111.
  10299. The extendible hash table can shrink in an analogous way. When you delete a
  10300. record from a disk page, if the number of the records on that page has fallen
  10301. below some threshhold, then check the buddy page to see if it has room to hold
  10302. the records from both pages. If two pages can be coalesced, then you may also
  10303. be able to halve the size of the page table.
  10304. An objection to extendible hashing is the need for additional memory
  10305. requirements to store the page table. For many applications, though, only a
  10306. modest amount of memory is needed. For example, suppose you use this algorithm
  10307. on a PC. By reserving 64K bytes (32K two-byte integers) for the page table and
  10308. using a 4K disk page size, you can handle a database of up to 128M bytes (32K
  10309. pages of 4K bytes each)!
  10310. I presented only the fundamentals of extendible hashing here; there are
  10311. several published improvements and variations. There is also an alternative,
  10312. called linear hashing, that requires no page table in memory, but is a bit
  10313. slower than one disk read per record fetch. There is no room to discuss all
  10314. these altenatives, but see the references listed at the end of this article
  10315. for details.
  10316.  
  10317.  
  10318. Collisions
  10319.  
  10320.  
  10321. Collisions are annoying for implementors of the hashing algorithm. Even though
  10322. the odds of a collision may be small (as they will be if you have a good
  10323. hashing function and fill only a small percentage of the hash table), you
  10324. always have to check for collision.
  10325. One way to remove the annoyance without actually solving the problem is to
  10326. find applications where collisions don't matter. There are quite a number of
  10327. these applications and they have the following in common: they can live with
  10328. answers that are right most of the time.
  10329. A good example of an application that can live with collisions is
  10330. checksumming. When you send a packet of data across a noisy telephone line,
  10331. you need to ensure that the data arrived correctly. You can hash the packet
  10332. data and append the hash value to the packet. The receiver can then use the
  10333. same hash function on the received data and, if the result is not the same as
  10334. the hash value received, request a retransmission (since the data must have
  10335. become corrupted). The hash or checksum function can be designed so that the
  10336. probability of a corrupted packet having the same hash value as the original
  10337. data is acceptably low.
  10338. Another situation where 100 percent accuracy is not required is using a hash
  10339. function to tell you when it is safe to avoid doing extra work. An interesting
  10340. example arises in writing an SQL query processor. An SQL query may generate a
  10341. table of records that contains duplicate rows and the user has the option of
  10342. requesting that duplicates be removed from the query result.
  10343. The brute force solution is to sort the table when uniqueness is required and
  10344. strip the duplicates. Before going to that extra effort, it would be nice to
  10345. know whether or not there are any duplicate rows. You can accomplish this by
  10346. combining hashing with a bit vector.
  10347. First, initialize a 64K bitmap (8K bytes) to zero. Then, as your SQL code
  10348. retrieves each row that matches the query, feed the entire row through a
  10349. hashing function that produces a number between zero and 64K. Using that hash
  10350. value as a bit offset, check that entry in the bitmap. If the bit is on, then
  10351. set a flag indicating that duplicate removal is required. If the bit is not
  10352. on, set the bit and continue.
  10353. This application doesn't care if there are collisions, just so long as
  10354. collisions are fairly rare. The only penalty for a collision occurring is an
  10355. unnecessary operation (removal of duplicates) won't be avoided. The
  10356. performance benefits are worth some effort to keep the number of collisions
  10357. down.
  10358. The obvious way to reduce the number of collisions is to use a very large bit
  10359. vector. Large vectors aren't always practical, since the bit vector needs to
  10360. contain at least R*N bits, where R is the number of records to be hashed and
  10361. 1/N is the highest acceptable probability of collision. If you want to handle
  10362. a query that may return 64K records and to limit the probability of an
  10363. unnecessary sort to 0.1, then you need at least 640K (64K*10) bits (more, if
  10364. the hashing function does not have a perfectly uniform distribution).
  10365. On the other hand, if you have plenty of room for a large bit vector, you can
  10366. use that extra space to further decrease the probability of collisions. Send
  10367. each record through three different hashing functions instead of only one.
  10368. Now, collisions are indicated only if all three bits indexed by the hashed
  10369. values are already set. If there is no collision, then turn on the three bits.
  10370. You can use any number of hashing functions, but more than three often
  10371. produces diminishing returns.
  10372.  
  10373.  
  10374.  
  10375. Building A Hash Function
  10376.  
  10377.  
  10378. Hashing applications depend on having suitable hashing functions. A good
  10379. hashing function is efficient and scatters the keys uniformly. If the function
  10380. is slow, it defeats the purpose: fast, direct access. If the function does not
  10381. distribute keys uniformly, then collisions will be the rule rather than the
  10382. exception.
  10383. In constructing a hashing function for a given application, the table size,
  10384. and also the range of hashing function results, is often determined by the
  10385. data. For example, if you need a hash table to handle 50,000 entries, with the
  10386. chance of collision at 50 percent or less, the hashing function must map the
  10387. keys into the range, zero to 100,000.
  10388. The hash() function at the beginning of this article illustrates applying the
  10389. modulus operator (%) to make the hashing function fit the table size. It turns
  10390. out, however, that the modulus operator is a decent hash function by itself if
  10391. the table size is a prime number. Usually, making the hash table a little
  10392. bigger than needed doesn't hurt, so a good choice for table size is the
  10393. smallest prime number greater than the estimated table size.
  10394. For an associative array class for C++, I needed to construct hash tables of
  10395. any size on the fly. I wanted to pick table sizes that were prime numbers near
  10396. the size specified by the caller, but I certainly didn't want to calculate
  10397. prime numbers every time a table was created. And storing a large table of
  10398. primes in memory was also unacceptable.
  10399. Listing 1 is a C++ program that solves this problem. The program generates a
  10400. logarithmic table of prime numbers that can then be compiled into a program
  10401. that selects hash table sizes. The table is constructed so that it contains a
  10402. prime number within roughly 20 percent of any desired hash table size. Twenty
  10403. percent is close enough for most purposes and the entire range of 16-bit
  10404. numbers is covered by a table that consumes only 88 bytes.
  10405. Having solved the problem of fitting the hash to a desired range, let's look
  10406. at how to get fast, uniform hashing functions in the first place. My current
  10407. preference is a simple function that uses a 256-byte character transformation
  10408. table.
  10409. To use this hashing function algorithm, first construct a 256-byte array in
  10410. which the 0th byte has the value 0, the first byte has the value 1, and so on.
  10411. Next, shuffle the values in this array as follows: for each element in the
  10412. array, pick a random number from 0 to 255 and exchange the current element
  10413. with the array element indexed by the random number.
  10414. Now the table performs a random character transformation. A hashing function
  10415. that uses this table can be as simple as:
  10416. int hash(char *key) {
  10417. extern table[256];
  10418. int h=0;
  10419. while (*key)
  10420. h = table[h^*key++];
  10421. return h;
  10422. }
  10423. The hashing function takes a "random walk" through the character
  10424. transformation table; wherever it ends up at the end of the key is the value
  10425. for the hash.
  10426. You will usually want at least a two-byte hash value rather than just a
  10427. one-byte value. To get a two-byte hash, compute the left-most byte as shown
  10428. and compute the right-most byte with the same formula, except add one to the
  10429. first character in the key. Adding one to the first character starts the
  10430. random walk at a different spot in the table and the result bears no
  10431. relationship to the other hash byte. You can form a three-or four-byte hash in
  10432. an analogous manner.
  10433.  
  10434.  
  10435. Perfect Hashing
  10436.  
  10437.  
  10438. The previous algorithm depends on a table constructed at random, so there is a
  10439. small, but non-zero probability that a very poor hashing function may have
  10440. been created. You can guard against poor functions with the following steps.
  10441. First, construct a data file that contains a large number of keys
  10442. representative of the data the function must be good at hashing. Second, write
  10443. a program that tests hashing functions against the keys in your test file.
  10444. This program is basically a loop that contains two actions: reshuffle the
  10445. character transformation table and apply the resulting hash function to the
  10446. data file to see how well it performs.
  10447. The program is looking for a shuffled table that generates a hash function
  10448. that produces few collisions as possible. An easy way to count collisions is
  10449. to allocate a bit vector that has twice as many bits as there are keys in your
  10450. sample data file -- there's no point in allocating a table that can hold any
  10451. data, since all you are interested in is counting collisions. This program
  10452. also has to save a copy of the most successful transformation table found so
  10453. far.
  10454. This program is appealing: you can start it up and have it look for a good
  10455. hashing function all day long while you do something else. This leads to an
  10456. interesting thought: what if you tell the program to keep going until it finds
  10457. a hashing function that produces no collisions whatsoever? The program may
  10458. take forever to find such a function and even if it does, the hashing function
  10459. will probably produce collisions on any other set of keys. There is an
  10460. application for this technique, however.
  10461. A hashing function that produces no collisions for a given set of keys is
  10462. called a perfect hashing function. If that hashing function also maps those
  10463. keys onto consecutive numbers with no gaps, then it is called a minimal
  10464. perfect hashing function. One application which has to deal with a small,
  10465. constant set of keys is the lexical analyzer of a compiler.
  10466. Part of a C++ book I am writing involves a compiler for a little language that
  10467. reserves the following keywords:
  10468. break else local
  10469. class exit new
  10470. const for return
  10471. continue foreach sysconst
  10472. delete function while
  10473. do if
  10474. When the lexical analyzer encounters an identifier, it must efficiently figure
  10475. out whether or not the identifier is a keyword. Keywords are mapped to
  10476. integers for ease of use (it is easier to store if as a 5 than to keep the
  10477. string if and do string comparisons).
  10478. Listing 2 contains a C++ program that attempts to find a perfect hashing
  10479. function for an input file of keywords. Rather than just reshuffling the
  10480. character transformation table at random and hoping for the best, this program
  10481. uses heuristics to try to swap just a few table entries as it tries to achieve
  10482. a perfect hash. In order to shorten the program, I've left out the code for
  10483. writing the character table out as a compilable program.
  10484. The real beauty of this hashing function is that the same function can be used
  10485. for both perfect hashing of keywords and general-purpose hashing of
  10486. identifiers. The program produced a hashing function that maps the keywords
  10487. into the integers zero through 17. In the lexical analyzer, I form a two-byte
  10488. hash, as shown earlier. If the first byte lies in the range zero to 17, then
  10489. it may be a keyword and must be checked. Non-keywords use the two-byte hash as
  10490. an index into it in the symbol table.
  10491.  
  10492.  
  10493. Further Reading
  10494.  
  10495.  
  10496. As always, Knuth (in this case, Volume 3, Sorting and Searching) is a good
  10497. place to start reading. The character transformation table method of hashing
  10498. and the perfect hashing algorithm were both derived from an article by Peter
  10499. K. Pearson in June 1990 issue of Communications of the ACM.
  10500. I finally found a book that does a decent job of covering the advances in file
  10501. organization algorithms of the last ten years and, in particular, hashing
  10502. algorithms. That book is File Organization and Processing, Alan L. Tharp
  10503. (1988, Wiley) and it covers most of the algorithms mentioned in this article
  10504. (and a good many more) in enough detail for you to derive your own
  10505. implementations.
  10506. Figure 1
  10507. Figure 2
  10508.  
  10509. Listing 1 (mkprmc.)
  10510. /*
  10511. (C) Copyright 1990 Ron Burk
  10512. All Rights Reserved
  10513.  
  10514. mkprm.c - Generate htabp.c, a list of prime numbers.
  10515.  
  10516.  
  10517. This program generates a logarithmic table of prime numbers. The
  10518. table is stored as source code that can be compiled into another program.
  10519.  
  10520. */
  10521.  
  10522. #include <stdio.h>
  10523. #include <stdlib.h>
  10524. #include <limits.h>
  10525.  
  10526. typedef unsigned long ulong;
  10527.  
  10528. class outfile
  10529. {
  10530. FILE *fout; // handle to output file
  10531. int nprimes;
  10532. void printhead();
  10533. void printtail();
  10534. public:
  10535. outfile() : fout(NULL), nprimes(0) { };
  10536. ÿoutfile() { close(); }
  10537. void open(const char *fname);
  10538. void close() { printtail(); fclose(fout); }
  10539. void prime(ulong); // format prime # into C initialized array
  10540. };
  10541.  
  10542.  
  10543. // PERCENTAGE specifies the allowable percentage discrepancy between
  10544. // the desired table size and the nearest prime number in the table.
  10545.  
  10546. const PERCENTAGE = 20;
  10547.  
  10548.  
  10549. // MAX_PRIME limits the range of numbers which will be searched for
  10550. // primes. Note that USHRT_MAX is guaranteed to be >= 65,535
  10551.  
  10552. const unsigned MAX_PRIME = USHRT_MAX;
  10553.  
  10554.  
  10555. const MAX_NPRIMES = 100 + MAX_PRIME / 10;
  10556.  
  10557. const char OUTFILE[] = "htabp.c";
  10558.  
  10559. int main(int /*argc*/, char /***argv*/)
  10560. {
  10561. unsigned previous = 0, log = 10;
  10562. static unsigned primes[MAX_NPRIMES];
  10563. int nprimes = 1;
  10564. int nprint = 0;
  10565. outfile fout;
  10566.  
  10567. fout.open(OUTFILE);
  10568. primes[0] = 2;
  10569. for(unsigned num = 3; num < MAX_PRIME-log && nprimes < MAX_NPRIMES; num+=2)
  10570. {
  10571. for(int i = 0; i < nprimes; ++i)
  10572. if(!(num%primes [i]))
  10573. break;
  10574. else if(num/primes[i] <= primes[i])
  10575. {
  10576.  
  10577. primes [nprimes++] = num;
  10578. if(num > previous + log)
  10579. {
  10580. ++nprint;
  10581. previous = num;
  10582. log = num / (100 / PERCENTAGE);
  10583. fout.prime(num);
  10584. printf( "%u\n", num );
  10585. }
  10586. break;
  10587. }
  10588. }
  10589. num = primes [nprimes-1];
  10590. if(num != previous)
  10591. {
  10592. fout.prime(num);
  10593. printf( "%u\n", num );
  10594. ++nprint;
  10595. }
  10596. printf( "nprimes = %d nprint = %d\n", nprimes, nprint );
  10597. }
  10598.  
  10599. void outfile::open(const char * fname)
  10600. {
  10601. fout = fopen(fname, "w");
  10602.  
  10603. if(fout == NULL)
  10604. }
  10605. fprintf(stderr, "Can't open output file '%s' for writing.\n",
  10606. fname);
  10607. exit(EXIT_FAILURE);
  10608. }
  10609. }
  10610.  
  10611. void outfile::prime(ulong p)
  10612. {
  10613. int boundary = !(nprimes%4);
  10614.  
  10615. if(nprimes == 0)
  10616. printhead();
  10617. if(nprimes)
  10618. {
  10619. fprintf(fout, ",");
  10620. if(boundary)
  10621. fprintf(fout, "\n");
  10622. }
  10623. if(boundary)
  10624. fprintf(fout, " ");
  10625. fprintf(fout, "%12lu", p);
  10626. ++nprimes;
  10627. }
  10628.  
  10629. void outfile::printhead()
  10630. {
  10631. char *type;
  10632.  
  10633. fprintf(fout,
  10634.  
  10635. "// machine generated; DO NOT EDIT\n"
  10636.  
  10637. "static unsigned short Primes [] = \n"
  10638. " {\n"
  10639.  
  10640. );
  10641. }
  10642.  
  10643. void outfile::printtail()
  10644. {
  10645. fprintf(fout,
  10646.  
  10647. "\n"
  10648. " };\n"
  10649. "const NPRIMES = %d;\n",
  10650.  
  10651. nprimes
  10652. );
  10653. }
  10654.  
  10655. /* End of File */
  10656.  
  10657.  
  10658. Listing 2 (makehash.c)
  10659. /*
  10660. * (C) Copyright 1990 Ron Burk
  10661. * All Rights Reserved
  10662. *
  10663. * makehash.c - program to make hashing functions.
  10664. *
  10665. * Note that this is a C++ program. It has been compiled
  10666. * with Turbo C++ and Zortech C++. There is conditional
  10667. * code to hack around the fact that Zortech does not
  10668. * allow a member function and a const member function of
  10669. * the same name.
  10670. */
  10671.  
  10672. #ifdef__ZTC______LINEEND____
  10673. #define CONST
  10674. #else
  10675. #define CONST const
  10676. #endif
  10677.  
  10678. #include <assert.h>
  10679. #include <ctype.h>
  10680. #include <limits.h>
  10681. #include <stdio.h>
  10682. #include <stdlib.h>
  10683. #include <string.h>
  10684.  
  10685. typedef unsigned char uchar;
  10686. enum bool { FALSE, TRUE };
  10687.  
  10688. FILE *OpenFile(const char *FileName, const char *IOMode)
  10689. {
  10690. assert(FileName != 0);
  10691. if(IOMode == 0)
  10692. IOMode = "r";
  10693. FILE *FileDescriptor = fopen(FileName, IOMode);
  10694. if(FileDescriptor == 0)
  10695. {
  10696.  
  10697. fprintf(stderr, "'%s': Can't open for mode '%s'\n",
  10698. FileName, IOMode);
  10699. exit(EXIT_FAILURE);
  10700. }
  10701. return FileDescriptor;
  10702. }
  10703.  
  10704. char *strdup(const char *s, size_t len)
  10705. {
  10706. char *ret = (char *)malloc(len+1);
  10707. memcpy(ret, s, len);
  10708. ret[len] = '\0';
  10709. return ret;
  10710. }
  10711.  
  10712. inline char *strdup(const char *s)
  10713. {
  10714. return strdup(s, strlen(s));
  10715. }
  10716.  
  10717. class KeyWord
  10718. {
  10719. public:
  10720. KeyWord(char *Name=0, char *Value=0);
  10721. char *Name()
  10722. { return Name_; }
  10723. char *Value()
  10724. { return Value_; }
  10725. bool Read(FILE *FileDescriptor);
  10726. private:
  10727. char *Name_;
  10728. char *Value_;
  10729. };
  10730.  
  10731. class KeyTable
  10732. {
  10733. public:
  10734. KeyTable(const char *FileName);
  10735. int NumberOfKeys()
  10736. { return NumberOfKeys_; }
  10737. KeyWord *operator[] (int KeyWordNumber);
  10738. void MoveTo(int Position1, int Position2);
  10739. static const int MAXKEYWORDS;
  10740. private:
  10741. int NumberOfKeys_;
  10742. KeyWord **Table;
  10743. };
  10744. const int KeyTable::MAXKEYWORDS = 100;
  10745.  
  10746. class ByteTable
  10747. {
  10748. public:
  10749. ByteTable();
  10750. void SetAscending();
  10751. void Shuffle(int Shuffles=1);
  10752. int Index(int Value);
  10753. uchar &operator[](int ByteNumber);
  10754. #ifndef __ZTC______LINEEND____
  10755. uchar operator[](int ByteNumber) const;
  10756.  
  10757. #endif
  10758. ByteTable &operator=(int Value);
  10759. private:
  10760. uchar *Table;
  10761. };
  10762.  
  10763. // KeyWord member functions
  10764. KeyWord::KeyWord(char *Name__, char *Value__)
  10765. : Name_(Name__), Value__(Value__)
  10766. {
  10767. }
  10768.  
  10769. /********************************
  10770. KeyWord::Read - read a KeyWord from an open file.
  10771.  
  10772. Each line in the KeyWord file must be a KeyWord line, an empty line,
  10773. or a comment. A KeyWord line is a line that contains a KeyWord
  10774. optionally followed by white space and a value. The KeyWord and the
  10775. value can contain any characters except white space (except the KeyWord
  10776. cannot begin with '#'). An empty line is a line that only contains
  10777. white space. A comment is any line that begins with a '#' (optionally
  10778. preceeded by white space).
  10779.  
  10780. ********************************/
  10781.  
  10782. bool KeyWord::Read(FILE *FileDescriptor)
  10783. {
  10784. const int MAXLINE = 256;
  10785. char Line[MAXLINE+1];
  10786.  
  10787. Name_ = Value_ = 0;
  10788. while(fgets(Line, MAXLINE, FileDescriptor))
  10789. {
  10790. char *Scanner = Line;
  10791. while(*Scanner && isspace(*Scanner)) // allow leading white space
  10792. ++Scanner;
  10793. if(*Scanner && *Scanner != '#') // if not empty and not comment
  10794. {
  10795. Name_ = Scanner; // remember start of KeyWord name
  10796. while(*Scanner && !isspace(*Scanner))
  10797. ++Scanner; // skip over name
  10798. if(*Scanner) // if something follows the name
  10799. {
  10800. *Scanner++ = '\0'; // NUL-terminate the name
  10801. while(*Scanner && isspace(*Scanner))
  10802. ++Scanner;
  10803. if(*Scanner)
  10804.  
  10805. {
  10806. Value = Scanner; // remember start of value
  10807. while(*Scanner && !isspace(*Scanner))
  10808. ++Scanner;
  10809. *Scanner = '\0';
  10810. Value_ = strdup(Value_); // make permanent copy
  10811. }
  10812. }
  10813. Name_ = strdup(Name_); // make permanent copy
  10814. return TRUE;
  10815. }
  10816.  
  10817. }
  10818.  
  10819. return FALSE; // hit End-Of-File
  10820. }
  10821.  
  10822. // KeyTable member functions
  10823.  
  10824. KeyTable::KeyTable(const char *FileName)
  10825. {
  10826. Table = new KeyWord *[MAXKEYWORDS];
  10827. assert(Table != 0);
  10828. FILE *KeyWordFile = OpenFile(FileName, "r");
  10829. KeyWord CurrentKeyWord;
  10830. for(int KeyWordNumber=0; CurrentKeyWord.Read(KeyWordFile); ++KeyWordNumber)
  10831. {
  10832. Table[KeyWordNumber] = new KeyWord;
  10833. *Table[KeyWordNumber] = CurrentKeyWord;
  10834. printf( "name='%s', value='%s'\n", Table[KeyWordNumber]->Name(),
  10835. Table[KeyWordNumber]->Value());
  10836. }
  10837. NumberOfKeys_ = KeyWordNumber;
  10838. printf( "read %d keywords\n", KeyWordNumber);
  10839. }
  10840.  
  10841. KeyWord *KeyTable::operator[] (int KeyWordNumber)
  10842. {
  10843. assert(KeyWordNumber >= 0);
  10844. assert(KeyWordNumber < NumberOfKeys());
  10845. return Table[KeyWordNumber];
  10846. }
  10847. void KeyTable::MoveTo(int MovePosition, int ToPosition)
  10848. {
  10849. KeyWord *Value = Table[MovePosition];
  10850. int Position;
  10851. for(Position=MovePosition; Position < NumberOfKeys()-1; ++Position)
  10852. Table[Position] = Table[Position+1];
  10853. for(Position=NumberOfKeys()-1; Position >= ToPosition; --Position)
  10854. Table[Position] = Table[Position-1];
  10855. Table[ToPosition] = Value;
  10856. }
  10857.  
  10858. // ByteTable member functions
  10859.  
  10860. ByteTable::ByteTable()
  10861. {
  10862. Table = new uchar[256];
  10863. assert(Table != 0);
  10864. memset(Table, 0, 256);
  10865. }
  10866.  
  10867. void ByteTable::SetAscending()
  10868. {
  10869. for(int ByteNumber=0; ByteNumber < 256; ++ByteNumber)
  10870. Table[ByteNumber] = ByteNumber;
  10871. }
  10872.  
  10873. void ByteTable::Shuffle(int Shuffles)
  10874. {
  10875. // swap each entry in the table with a randomly chosen entry
  10876.  
  10877.  
  10878. for(int PassNumber=0; PassNumber < Shuffles; ++PassNumber)
  10879. {
  10880. for(int ByteNumber=0; ByteNumber < 256; ++ByteNumber)
  10881. {
  10882. int RandomPartner = rand() % 256;
  10883. uchar OtherValue = Table[RandomPartner];
  10884. Table[RandomPartner] = Table[ByteNumber];
  10885. Table[ByteNumber] = OtherValue;
  10886. }
  10887. }
  10888. }
  10889.  
  10890. uchar &ByteTable::operator[](int ByteNumber)
  10891. {
  10892. assert(ByteNumber >= 0);
  10893. assert(ByteNumber <= 255);
  10894.  
  10895. return Table[ByteNumber];
  10896. }
  10897. #ifndef__ZTC______LINEEND____
  10898. uchar ByteTable::operator[](int ByteNumber) const
  10899. {
  10900. assert(ByteNumber >= 0);
  10901. assert(ByteNumber <= 255);
  10902.  
  10903. return Table[ByteNumber];
  10904. }
  10905. #endif
  10906.  
  10907. int ByteTable::Index(int Value)
  10908. {
  10909. uchar *Found = (uchar *)memchr(Table, Value, 256);
  10910. if(Found)
  10911. return Found - Table;
  10912. else
  10913. return -1;
  10914. }
  10915.  
  10916. inline
  10917. ByteTable &ByteTable::operator=(int Value)
  10918. {
  10919. assert(Value >= 0);
  10920. assert(Value <= 255);
  10921. memset(Table, Value, 256);
  10922. return *this;
  10923. }
  10924.  
  10925. int ByteHash(const char *Text, const ByteTable &HashTable)
  10926. {
  10927. int Hash = 0;
  10928. while(*Text)
  10929. Hash = HashTable[*Text++ ^ Hash];
  10930. return Hash;
  10931. }
  10932.  
  10933. int MakeHash(const char *KeyWordFileName);
  10934.  
  10935. void Usage()
  10936.  
  10937. {
  10938. fprintf(stderr, "Usage: makehash keyfile\n");
  10939. exit(EXIT_FAILURE);
  10940. }
  10941.  
  10942. int main(int argc, char **argv)
  10943. {
  10944. if(argc < 2)
  10945. Usage();
  10946. char *KeyWordFileName = argv[1];
  10947. exit(MakeHash(KeyWordFileName));
  10948. }
  10949.  
  10950. int MakeHash(const char *KeyWordFileName)
  10951. {
  10952. KeyTable InputTable(KeyWordFileName);
  10953. int iKeyWord;
  10954. int *Failures = new int[InputTable.NumberOfKeys()];
  10955.  
  10956. assert(Failures != 0);
  10957. ByteTable HashBytes;
  10958. for(int Attempts=0; Attempts < 999; ++Attempts)
  10959. {
  10960. for(int xx=0; xx < InputTable.NumberOfKeys(); ++xx) Failures[xx]=0;
  10961. for(int TableBase = 0; TableBase < 256; ++TableBase)
  10962. {
  10963. HashBytes.SetAscending(); // set equal to 0,1,2,...255
  10964. HashBytes.Shuffle(); // randomize
  10965. int NotEligible[256];
  10966. for(int x=0; x < 256; ++x) NotEligible[x] = 0;
  10967. for(int iKeyWord = 0; iKeyWord < InputTable.NumberOfKeys(); ++iKeyWord)
  10968. {
  10969. const char *Text = InputTable[iKeyWord]->Name();
  10970. int TextLength = strlen(Text);
  10971. int RandomWalk[99];
  10972. int Hash = 0;
  10973. uchar H[99];
  10974. for(int i = 0; i < TextLength; ++i)
  10975. {
  10976. int j = Hash ^ Text[i];
  10977. Hash = H[i] = HashBytes[j];
  10978. ++NotEligible[j];
  10979. RandomWalk[i] = j;
  10980. }
  10981. int DesiredValue = (iKeyWord + TableBase) % 256;
  10982. for(i = TextLength-1; i >= 0; --i)
  10983. {
  10984. int Pos = RandomWalk[i];
  10985. --NotEligible[Pos];
  10986. int Other = HashBytes.Index(DesiredValue);
  10987.  
  10988. assert(Other >= 0);
  10989. if(NotEligible[Pos] == 0 && NotEligible[Other] == 0)
  10990. {
  10991. HashBytes[Other] = HashBytes[Pos];
  10992. HashBytes[Pos] = DesiredValue;
  10993. ++NotEligible[Other];
  10994. ++NotEligible[Pos];
  10995. assert(ByteHash(Text, HashBytes)
  10996.  
  10997. == (iKeyWord + TableBase)%256);
  10998. break;
  10999. }
  11000. else // else not eligible for swapping
  11001. {
  11002. DesiredValue = Other ^ Text[i];
  11003. ++NotEligible[Other];
  11004. }
  11005. }
  11006. if(i < 0)
  11007. break;
  11008. }
  11009. if[iKeyWord >= InputTable.NumberOfKeys())
  11010. {
  11011. for(iKeyWord = 0; iKeyWord < InputTable.NumberOfKeys(); ++iKeyWord)
  11012. {
  11013. const char *Name = InputTable[iKeyWord]->Name();
  11014. printf("Hash('%s') = %d\n", Name, ByteHash(Name, HashBytes));
  11015. }
  11016. return EXIT_SUCCESS;
  11017. }
  11018. else
  11019. {
  11020. if(++Failures[iKeyWord] > 20)
  11021. {
  11022. InputTable.MoveTo(iKeyWord, 0);
  11023. break;
  11024. }
  11025. }
  11026. }
  11027. }
  11028.  
  11029. return EXIT_FAILURE;
  11030. }
  11031.  
  11032. /* End of File */
  11033.  
  11034.  
  11035.  
  11036.  
  11037.  
  11038.  
  11039.  
  11040.  
  11041.  
  11042.  
  11043.  
  11044.  
  11045.  
  11046.  
  11047.  
  11048.  
  11049.  
  11050.  
  11051.  
  11052.  
  11053.  
  11054.  
  11055.  
  11056.  
  11057.  
  11058.  
  11059.  
  11060. Data Compression Using Huffman Coding
  11061.  
  11062.  
  11063. Dwayne Phillips
  11064.  
  11065.  
  11066. Dwayne Phillips works as a computer and electronics engineer with the United
  11067. States Department of Defense. He has a Ph.D. in electrical and computer
  11068. engineering at Louisiana State University. His interests include computer
  11069. vision, artificial intelligence, software engineering, and programming
  11070. languages.
  11071.  
  11072.  
  11073. One of the best known data compression schemes is Morse code. Morse code uses
  11074. few dots and dashes for frequently occurring letters such as e and t, and more
  11075. dots and dashes for infrequent letters such as x and z. Compare this scheme to
  11076. the ASCII code which uses seven bits for all the letters and symbols. The
  11077. ASCII code is clearly inferior when it comes to the length of a message.
  11078. Huffman coding[1] uses an idea similar to Morse code. In Huffman coding, you
  11079. represent frequently occurring characters with a small number of bits, and
  11080. infrequent characters with a larger number of bits. We could always represent
  11081. the character t by 01 instead of 1100001 (ASCII), e by 10 instead of 1100101
  11082. and so on. This would save space, but some files do not contain any t's or e's
  11083. and these short codes could be used for other characters. Adaptive coding
  11084. solves that problem. In adaptive coding, you examine a file, count the
  11085. occurrences of each character, and create a code unique to that file.
  11086. To compress a file using Huffman coding you (1) examine the file and count the
  11087. occurrences of characters, (2) create a new code for the characters in the
  11088. file, and (3) code the file using the new codes. To decompress a file, you (1)
  11089. read the unique code for the file, (2) read the compressed file, and (3)
  11090. decode the compressed file using the unique code. The trick in this process is
  11091. creating the new code and packing and unpacking bits. The source code
  11092. presented later will illustrate how to do that.
  11093. Huffman coding attempts to create a minimum redundancy code, that minimizes
  11094. the average number of bits per character. Let N = number of characters (256
  11095. for eight bit codes) and P(i) = probability of the ith character occurring.
  11096. This yields equation 1.
  11097. Equation 1
  11098. We let L(i) = number of bits to represent the ith character and we want to
  11099. minimize the average number of bits needed to code a file as shown in equation
  11100. 2.
  11101. Equation 2
  11102. There are two rules to follow when using Huffman coding. First, the code for
  11103. each character must be unique. Second, there will be no start of code or end
  11104. of code once the stream of bits begins. In ASCII you know when the code for
  11105. each character starts because they are all seven bits long. When you've coded
  11106. a file using Huffman coding, however, the codes for each character have
  11107. different lengths (some are two bits, some three bits, and so on). If you have
  11108. a start of code marker, you will waste space and defeat the purpose of data
  11109. compression.
  11110. In order to follow the second rule, you must abide by the first. This means
  11111. that no code can be the prefix of another code. The code for each character
  11112. must be unique. For example, if you represent an a by 01 and a b by 1, then
  11113. the bit stream 011101101 can be decoded to abbaba. Now suppose we needed a
  11114. code to represent c. We cannot represent c by 10 because the code for b (1) is
  11115. a prefix of the code for c (10). In such a case, the stream 011101101 could
  11116. either be abcbcb or abbaba. To correct this confusion, you could represent a
  11117. by 01, b by 1, and c by 00. The prefix condition is not violated and there
  11118. will not be any confusion when it's time to decompress the bit stream.
  11119. There is one more derived rule for Huffman coding. The following equation
  11120. states mathematically that the most frequently occurring characters will have
  11121. shorter codes. If two characters occur with the same frequency, then their
  11122. codes will be equal or differ only by one bit.
  11123. if P (a) Â£ P (b) Â£ P (c) then L (a) >= L (D) >= L
  11124. Figure 1 and Figure 2 illustrate the creation of Huffman codes. Figure 1 shows
  11125. a simple example. Suppose there is a file containing 10 as, five cs, and three
  11126. bs. Figure 1 shows the characters in the file arranged with the most frequent
  11127. letter at the top and with the number of occurrences next to each character.
  11128. You begin coding at the bottom with the least frequently occurring characters.
  11129. You link characters b and c by adding their numbers of occurrences and
  11130. connecting them with a line. You give the least occurring letter (b) a 1 bit
  11131. and the most occurring letter (c) a 0 bit. In the next step you link letter a
  11132. with the bc combination. You give the bc combination a 1 bit and the a a 0
  11133. bit. The codes for these characters are read backwards from right to left. The
  11134. new code for a is 0. The new code for b is 11 and for c is 10.
  11135. Now, if a part of the input file contained abcaaaccb, the coded bit stream
  11136. would be 0-11-10-0-0-0-10-10-11. This required only 14 bits. The ASCII coding
  11137. (seven bits per character) would require 63 bits. The entire file of 18
  11138. characters would only require 26 bits instead of 126 bits. The compressed
  11139. coding requires only 26/126 = 20.6 percent as much space. (There is, however,
  11140. the question of how to store the new code (a=0 b=11 c=10) at the front of the
  11141. compressed file without taking too much space.) Notice that the most
  11142. frequently occurring character, a, has the shortest code. Using equation 1 and
  11143. equation 2, the average number of bits per character is 1.44 instead of 7 for
  11144. ASCII.
  11145. Figure 2 shows a more complicated example. The coding process begins at the
  11146. bottom. Everything proceeds as before until you link c with the def
  11147. combination. At this point both a (10) and b (8) occur less frequently than
  11148. the cdef combination (12) so you must link a and b. After this you may link
  11149. the ab combination with cdef. You read the new codes backwards from right to
  11150. left and the result is shown at the bottom right of the figure. Again, using
  11151. equations 1 and 2, the average number of bits per character is 2.46 instead of
  11152. 7 for ASCII.
  11153. Now let's look at some code listings. Listing 1 shows the include file for the
  11154. program. Note the three data structures. The first structure, item_struct, is
  11155. used inside the program to create the new codes, code the characters in the
  11156. input file, and decode the bit stream in the compressed file. This structure
  11157. is easy to use inside the program, but is too long to store as the header to
  11158. the compressed file. The second, header_struct, is the one stored as the
  11159. header to the compressed file. It requires much less space than the
  11160. item_struct and still holds enough information to allow the compressed file to
  11161. be decompressed. This scheme enables you to experiment with different types of
  11162. file headers to store with the compressed file. If you can create a shorter,
  11163. more efficient file header, then you only need to change the conversion
  11164. routines and the read and write routines. If you used only one structure for
  11165. internal use and the file header, then you would need to change code
  11166. throughout the program whenever you thought of a better file header. The third
  11167. structure in Listing 1 is the constant CODE_LENGTH. This gives the maximum
  11168. number of bits you can use for a new code. This may not be big enough if you
  11169. are compressing a large file that has some very infrequent characters. The
  11170. more infrequent the character the longer its code. If you have problems with
  11171. large files you may need to increase this to 24 or 32.
  11172. Listing 2 shows the main routine of the program and several other functions.
  11173. The main routine interprets the command line and starts the compression or
  11174. decompression process. The function read_input_file_and_create_histogram
  11175. performs the first step of compression. It reads through the entire
  11176. uncompressed file and records the number of occurrences of each character.
  11177. This information will be passed to the functions in Listing 3 which will then
  11178. create the new code. This points out one of the disadvantages of Huffman
  11179. coding. In Huffman coding you must read the file twice. The first time you
  11180. count occurrences of characters and the second time you code the characters.
  11181. Also shown in Listing 2 are the functions convert_short_to_long and
  11182. convert_long_to_short. These functions convert the long item_struct to the
  11183. shorter header_struct discussed previously. If you create a better file header
  11184. for the compressed file, then you need to change these two conversion
  11185. functions.
  11186. Listing 3 shows the functions that perform the second step of compression.
  11187. They take the number of occurrences of each character and create the new code
  11188. for this particular file. The function create_huffman_code is the controlling
  11189. routine for this process. It first sorts the item_array so the most frequent
  11190. characters are at the "top" and the least frequent are at the "bottom" as in
  11191. the examples of Figure 1 and Figure 2. Next, it disables the characters that
  11192. do not occur in the input file. There is no need to process those characters.
  11193. Finally, it goes into a while loop of linking or combining the two smallest
  11194. items in the item_array until they are all linked. Most of the functions in
  11195. Listing 3 search the item_array, and find the characters to be linked.
  11196. The key functions in Listing 3 are code_smallest_item and
  11197. code_next_smallest_item. They attach the 1's and 0's to the characters. The
  11198. difficult part of this task is propagating the 1's and 0's to all the
  11199. characters linked together. Notice in Figure 2 that when cdef was linked to
  11200. ab, the 1 assigned to c propagated down to d, e, and f. At the end of
  11201. code_smallest_item the program loops through the item_array looking for links
  11202. to other characters. If it finds a link, it calls itself recursively using the
  11203. link (code_next_smallest_item does the same operation). The function
  11204. combine_2_ smallest_items created these links using the .includes part of the
  11205. item_array.
  11206. Listing 4 shows the final step of compression. These functions use the new
  11207. codes to code the input file into a compressed bit stream and write this to a
  11208. file. The function code_and_write_output_file controls the process. It reads a
  11209. character from the input file, uses the new code for that character to set
  11210. bits in the output buffer, and writes the output buffer to the output file.
  11211. One thing to watch in the output process is to wait until the bit stream in
  11212. the output buffer is on a byte boundary before writing to the file. The last
  11213. section of code in this function performs that task.
  11214. The key functions in Listing 4 are code_byte, set_bit_to_1, and
  11215. clear_bit_to_0. The code_byte function looks at the character to code, finds
  11216. its new code, and calls the other two functions to set the bits in the output
  11217. buffer. The bit setting functions set or clear a particular bit in the output
  11218. buffer. They use bit masks defined in the include file and bit-wise AND or
  11219. bitwise OR the mask with a byte in the output buffer.
  11220. Listing 5 shows the code to decompress a file. Decompression is easier and
  11221. quicker than compression. The new code already exists so you only need to
  11222. reverse the last step of the compression process. The function
  11223. decode_compressed_file controls the process. The first step is to read the new
  11224. codes from the file header. This comprises a file read and then a call to
  11225. convert_short_to_long to put the header into the item_struct format.
  11226. The function decode_file_and_write_output performs the last two steps of the
  11227. decompression process. It decodes the compressed bit stream and writes the
  11228. characters to the output file. To decode the compressed bit stream you must
  11229. look at the codes read from the file header and compare them one by one to the
  11230. bit stream. Recall that the codes are all unique and the prefix condition must
  11231. hold. Therefore, if you start at one end of the item_struct and compare each
  11232. code to the first bits in the bit stream, you will find a match. The program
  11233. uses two functions to do this. The function convert_bits_to_char looks at the
  11234. bit stream and translates the 1 and 0 bits to ONE and ZERO character
  11235. constants. The function decode_bits uses this stream of characters to search
  11236. through the item_struct. This makes the comparisons easier because the
  11237. item_struct holds the codes as characters. The convert_bits_to_char function
  11238. examines each bit using a shift bit and compare operation. This requires less
  11239. code than the set bit functions discussed earlier.
  11240. The decode_bits function looks at the input bits, matches them to a code in
  11241. the item_struct, and puts the decoded character in the output buffer. This
  11242. operation requires searching through the item_struct until you find a code
  11243. that matches the first bits in the bit stream. A strncmp performs the
  11244. comparisons. When the decode_bits finds a match, it writes the decoded
  11245. character to the output buffer.
  11246. This process of reading compressed bits, matching the codes, and writing out
  11247. the decoded characters continues until the compressed file is empty. At that
  11248. time the decompression process is finished.
  11249. There is always room for improvement in a program. The objective of this
  11250. program is to compress files. The biggest place for reducing the size of the
  11251. compressed file is in the file header. This must store the new codes for the
  11252. characters in the original file. The header_struct shown in Listing 1 contains
  11253. 4,356 bytes. This can no doubt be smaller. One idea is to keep only those
  11254. characters that occurred in the message instead of all 256. Another idea is to
  11255. pack the codes into bits instead of characters. The program is designed to
  11256. allow experiments in this area. The item_struct is used internally so you do
  11257. not need to make code changes everywhere. When you are ready to try a new file
  11258. header, change the conversion routines in Listing 2 (convert_long_to_short,
  11259. convert_short_to_long) and the write and read routines in Listing 4 and
  11260. Listing 5, (output_file_header) and (input_file_header).
  11261. Reference
  11262. 1. Huffman, David, "A Method for the Construction of Minimum-Redundancy
  11263. Codes," Proceedings of the IRE, Vol. 40, No. 9, pp. 1098-1101, 1952.
  11264. Figure 1
  11265. Figure 2
  11266.  
  11267. Listing 1 (cujhuff3.c)
  11268. /************************************************
  11269. *
  11270. * file d:\lsu\huffman.h
  11271. *
  11272. * Functions: This file contains no functions. It
  11273. * contains declarations of the data
  11274. * structures used by the hoffman coding
  11275. * program.
  11276. *
  11277. * Purpose: To declare data structures.
  11278. *
  11279. * Modifications:
  11280. *
  11281.  
  11282. *************************************************/
  11283.  
  11284.  
  11285. #include "d:\c600\include\stdio.h"
  11286. #include "d:\c600\include\graph.h"
  11287. #include "d:\c600\include\io.h"
  11288. #include "d:\c600\include\fcntl.h"
  11289. #include "d:\c600\include\dos.h"
  11290. #include "d:\c600\include\math.h"
  11291. #include "d:\c600\include\sys\types.h"
  11292. #include "d:\c600\include\sys\stat.h"
  11293. #include "d:\lsu\iptype.h"
  11294.  
  11295. #define LENGTH 256 /* length of item array */
  11296. #define LLENGTH 25 /* length of includes array */
  11297. #define ONE '1'
  11298. #define ZERO '0'
  11299. #define OTHER '2'
  11300. #define CODE_LENGTH 16 /* max # of bits for a character */
  11301. #define IB_LENGTH 150
  11302. #define OB_LENGTH 1000
  11303. #define END_FILE 254
  11304.  
  11305. /* The following constants are for setting, clearing, and
  11306. testing bits in a char buffer */
  11307.  
  11308. #define SET_BIT_SEVEN 1
  11309. #define SET_BIT_SIX 2
  11310. #define SET_BIT_FIVE 4
  11311. #define SET_BIT_FOUR 8
  11312. #define SET_BIT_THREE 16
  11313. #define SET_BIT_TWO 32
  11314. #define SET_BIT_ONE 64
  11315. #define SET_BIT_ZERO 128
  11316.  
  11317. #define CLEAR_BIT_ZERO 127
  11318. #define CLEAR_BIT_ONE 191
  11319. #define CLEAR_BIT_TWO 223
  11320. #define CLEAR_BIT_THREE 239
  11321. #define CLEAR_BIT_FOUR 247
  11322. #define CLEAR_BIT_FIVE 251
  11323. #define CLEAR_BIT_SIX 253
  11324. #define CLEAR_BIT_SEVEN 254
  11325.  
  11326. /**************************************************
  11327. *
  11328. * This is the item_struct which defines the
  11329. * item_array used throughout the Huffman
  11330. * program.
  11331. *
  11332. * indicator - shows whether or not to process
  11333. * an element of the item array. The values
  11334. * can be D=disable or E=enalble.
  11335. *
  11336. * character - the original character in the big
  11337. * file. This can be any character from
  11338. * decimal 0 to 255.
  11339. *
  11340. * count - counts the number of times a character
  11341. * appears in the original big file.
  11342. *
  11343.  
  11344. * coded[CODE_LENGTH] - this holds the code for a
  11345. * character. It is of the form 101101222222.
  11346. * The '2' is the OTHER character which means
  11347. * it is not used. e.g. if code=10122222, then
  11348. * the code is 101 and the 22222 are dummy
  11349. * characters.
  11350. *
  11351. * includes - this is a number that links the item
  11352. * to any other item with which it has been
  11353. * combined.
  11354. *
  11355. **************************************************/
  11356.  
  11357. struct item_struct{
  11358. char indicator;
  11359. char character;
  11360. long count;
  11361. char coded[CODE_LENGTH];
  11362. short includes[LLENGTH];
  11363. };
  11364.  
  11365. /*************************************************
  11366. *
  11367. * This is the header_struct. We'll save this
  11368. * at the beginning of the compressed file. It
  11369. * is smaller than the item_struct and will save
  11370. * some space in the compressed file.
  11371. *
  11372. *************************************************/
  11373.  
  11374. struct short_item{
  11375. char character;
  11376. char coded[CODE_LENGTH];
  11377. };
  11378. struct header_struct{
  11379. struct short_item items[LENGTH];
  11380. long in_file_length;
  11381. };
  11382. /* End of File */
  11383.  
  11384.  
  11385. Listing 2 (huffman.c)
  11386. /******************************************************
  11387. *
  11388. * file d:\huffman.c
  11389. *
  11390. *
  11391. ******************************************************/
  11392.  
  11393.  
  11394. #include "d:\lsu\cujhuff.h"
  11395.  
  11396. main(argc, argv)
  11397. int argc;
  11398. char *argv[];
  11399. {
  11400. char input_file_name [80],
  11401. output_file_name [80],
  11402. r[80];
  11403.  
  11404.  
  11405. int i, j;
  11406. struct item_struct item_array[LENGTH];
  11407. struct header_struct file_header;
  11408.  
  11409.  
  11410. if(argc < 3){
  11411. printf("\nHUFFMAN> You did not enter enough file names.");
  11412. printf("\nHUFFMAN> Try again: huffman in_file_name out_file_name");
  11413. printf("\nHUFFMAN> or");
  11414. printf("\nHUFFMAN> huffman destination_file packed_file d");
  11415. exit(1);
  11416. }
  11417.  
  11418. /*
  11419. If there are four arguments then you are decompressing
  11420. the compressed input file to a full size output file.
  11421. */
  11422.  
  11423. if(argc >= 4){
  11424. strcpy(output_file_name, argv[1]);
  11425. strcpy(input_file_name, argv[2]);
  11426. decode_compressed_file(input_file_name,
  11427. output_file_name,
  11428. item_array,
  11429. &file_header);
  11430. }
  11431.  
  11432. else{ /* else you compress the full size input file and write
  11433. out a compressed output file */
  11434.  
  11435. strcpy(input_file_name, argv[1]);
  11436. strcpy (output_file_name, argv[2]);
  11437. read_input_file_and_create_histogram(input_file_name, item_array);
  11438. sort_item_array (item_array);
  11439. printf("\n\nHUFFMAN> This is the sorted item array:\n");
  11440. print_item_array (item_array);
  11441. create_huffman_code(item_array);
  11442. printf("\n\nHUFFMAN> This is the Huffman coding of the
  11443. characters:\n");
  11444. print_item_array(item_array);
  11445. printf("\n> Coding the file");
  11446. convert_long_to_short(item_array, &file_header);
  11447. code_and_write_output_file(item_array,
  11448. input_file_name,
  11449. output_file_name,
  11450. &file_header);
  11451.  
  11452. } /* ends else compress input file to output file */
  11453.  
  11454. } /* ends main */
  11455.  
  11456. /*
  11457. read_input_file_and_create_histogram(...
  11458.  
  11459. Read the input file. Count up the occurances of each
  11460. character and create a histogram.
  11461. */
  11462.  
  11463.  
  11464. read_input_file_and_create_histogram(input_file_name, item_array)
  11465. char input_file_name[];
  11466. struct item_struct item_array[];
  11467. {
  11468. char buffer[1000];
  11469.  
  11470. int bytes_read,
  11471. i,
  11472. in_file_desc,
  11473. j;
  11474.  
  11475. clear_item_array (item_array);
  11476. in_file_desc = my_open(input_file_name);
  11477. printf("\n> in file desc = %d", in_file_desc);
  11478. bytes_read = 1000;
  11479.  
  11480. while(bytes_read == 1000){
  11481. bytes_read = my_read(in_file_desc, buffer, 1000);
  11482. for(i=0; i<bytes_read; i++) {
  11483. j = buffer[1];
  11484. item_array[j].count = item_array[j].count + 1;
  11485. } /* ends loop over i */
  11486. } /* ends while bytes_read == 1000 */
  11487.  
  11488. close (in_file_desc);
  11489.  
  11490. } /* ends read_input_file_and_create_histogram */
  11491.  
  11492. /*
  11493. clear_item_array(...
  11494.  
  11495. This function initializes the item_array.
  11496. */
  11497.  
  11498. clear_item_array(item_array)
  11499. struct item_struct item_array[];
  11500. {
  11501. int i,j, k;
  11502.  
  11503. for(i=0; i<LENGTH; i++)(
  11504. item_array[i].indicator = 'E';
  11505. item_array[i].character = i;
  11506. item_array[i].count = 0;
  11507. for(k=0; k<LLENGTH; k++)
  11508. item_array[i].includes[k] = 256;
  11509. for(j=0; j<CODE_LENGTH; j++)
  11510. item_array[i].coded[j] = OTHER;
  11511. } /* ends loop over i */
  11512. } /* ends clear_item_array */
  11513.  
  11514. /*
  11515. print_item_array (item_array)
  11516.  
  11517. This function is for debugging. It prints
  11518. to the screen the item_array.
  11519. */
  11520.  
  11521. print_item_array (item_array)
  11522. struct item_struct item_array[];
  11523.  
  11524. {
  11525. char response[5];
  11526. int i,
  11527. j,
  11528. k,
  11529. max_i,
  11530. printed;
  11531. float ratio;
  11532. long max;
  11533. printed = 0;
  11534. max = 0;
  11535.  
  11536. printf("\n>");
  11537. printed++;
  11538.  
  11539. for(i=0; i<LENGTH; i++){
  11540. if(item_array[i].count > max){
  11541. max = item_array[i].count;
  11542. max_i = i;
  11543. } /* ends if count > max */
  11544. } /* ends loop over i */
  11545.  
  11546. ratio = 30.0/(float) (item_array[max_i].count);
  11547.  
  11548. for(i=0; i<LENGTH; i++){
  11549. if(item_array[i].count != 0){
  11550. printed++;
  11551. if((printed%22) == 0){
  11552. printf("/n> Hit return to continue-");
  11553. read_string(response);
  11554. } /* ends if printed 20 lines */
  11555. printf("\n> [%3d]=%3d=%c=%4d=", i, item_array[i].character,
  11556. item_array[i].indicator,
  11557. item_array[i].count);
  11558. for(k=0; k<CODE_LENGTH; k++)
  11559. printf ("%c", item_array[i].coded[k]);
  11560. for(j=0; j<(ratio*item_array[i].count); j++){
  11561. printf("*");
  11562. } /* ends loop over j */
  11563. } /* ends if count != 0 */
  11564. } /* ends loop over i */
  11565. } /* ends print_item_array */
  11566.  
  11567. /*
  11568. convert_long_to_short (...
  11569.  
  11570. This function converts the long item_array into
  11571. a shorter file header.
  11572.  
  11573. */
  11574.  
  11575. convert_long_ to_ short(item_array, file_header)
  11576. struct item_struct item_array[];
  11577. struct header_struct *file_header;
  11578. {
  11579. int i, j, k;
  11580.  
  11581. for(i=0; i<LENGTH; i++){
  11582. file_header->items[i].character = item_array[i].character;
  11583.  
  11584. for(j=0; J<CODE_LENGTH; j++)
  11585. file_header->items[i].coded[j] = item_array[i].coded[j];
  11586. } /* ends loop over i */
  11587.  
  11588. } /* ends convert_long_to_short */
  11589.  
  11590. /*
  11591. convert_short_to_long (...
  11592.  
  11593. This function converts the short file header into
  11594. a the longer item_array for use in the program.
  11595.  
  11596. */
  11597.  
  11598. convert_short_to_long (item_array, file_header)
  11599. struct item_struct item_array[];
  11600. struct header_struct *file_header;
  11601. {
  11602. int i, j, k;
  11603.  
  11604. for(i=0; i<LENGTH; i++){
  11605. item_array[i].character = file_header->items[i].character;
  11606. for(j=0; J<CODE_LENGTH; j++)
  11607. item_array[i].coded[j] = file_header->items[i].coded[j];
  11608. } /* ends loop over i */
  11609.  
  11610. } /* ends convert_short_to_long */
  11611.  
  11612. /* End of File */
  11613.  
  11614.  
  11615. Listing 3 (cujhuff2.c)
  11616. /********************************************
  11617. *
  11618. * file d:\lsu\cujhuff2.c
  11619. *
  11620. ********************************************/
  11621.  
  11622. #include "d:\lsu\cujhuff.h"
  11623.  
  11624.  
  11625. /*
  11626. create_huffman_code(item_array)
  11627.  
  11628. This routine is the top of the routines that create
  11629. the codes. Create the codes xxxxx010 for the characters
  11630. read in. You use the item_array which contains the
  11631. counts of occurances and so on.
  11632. */
  11633.  
  11634.  
  11635. create_huffman_code (item_array)
  11636. struct item_struct item_array[];
  11637. {
  11638. int counter,
  11639. i,
  11640. not_ended;
  11641. struct item_struct temp;
  11642.  
  11643.  
  11644. sort_item_array(item_array);
  11645. disable_zero_counts(item_array);
  11646.  
  11647. /***********************************
  11648. *
  11649. * The following short loop is the
  11650. * heart of the algorithm. The
  11651. * rest is the implementation detail
  11652. * which is not trivial.
  11653. *
  11654. ************************************/
  11655.  
  11656. counter = 0;
  11657. not_ended = 1;
  11658. while(not_ended){
  11659. if( (counter % 50) == 0) printf("\n> Creating code ");
  11660.  
  11661. printf(".");
  11662. combine_and_code_2_smallest_items(item_array, ¬_ended);
  11663. sort_item_array(item_array);
  11664. counter++;
  11665. } /* ends while not_ended */
  11666.  
  11667. reverse_order_of_coded(item_array);
  11668.  
  11669. } /* ends create_huffman_code */
  11670.  
  11671.  
  11672. /*
  11673. sort_item_array(item_array)
  11674.  
  11675. This is a very simple bubble sort algorithm.
  11676. It does use the Microsoft C ability to
  11677. set one struct equal to another. Some
  11678. compilers do not support this.
  11679. */
  11680.  
  11681.  
  11682. sort_item_array(item_array)
  11683. struct item_struct item_array[];
  11684. {
  11685. int i,
  11686. not_finished,
  11687. swapped;
  11688. struct item_struct temp;
  11689.  
  11690. not_finished = 1;
  11691.  
  11692. while(not_finished){
  11693. swapped = 0;
  11694. for(i=0; i<LENGTH-1; i++){
  11695. if(item_array[i].count < item_array[i+1].count){
  11696. swapped = 1;
  11697. temp = item_array[i];
  11698. item_array[i] = item_array[i+1];
  11699. item_array[i+1] = temp;
  11700. } /* ends if you need to swap */
  11701. } /* ends loop over i */
  11702. if(swapped == 0)
  11703.  
  11704. not_finished = 0;
  11705. } /* ends while not_finished */
  11706.  
  11707.  
  11708. /* Perform an extra pass through the sort to
  11709. ensure all 'D'isbaled items are below all
  11710. 'E'nabled items in the item_array */
  11711.  
  11712. not_finished = 1;
  11713.  
  11714. while(not_finished){
  11715. swapped = 0;
  11716. for(i=0; i<LENGTH-1; i++){
  11717. if( (item_array[i].indicator == 'D') &&
  11718. (item_array[i+1].indicator == 'E')){
  11719. swapped = 1;
  11720. temp = item_array[i];
  11721. item_array[i] = item_array[i+1];
  11722. item array[i+1] = temp;
  11723. } /* ends if you need to swap */
  11724. } /* ends loop over i */
  11725. if(swapped == 0)
  11726. not_finished = 0;
  11727. } /* ends while not_finished */
  11728.  
  11729.  
  11730. } /* ends sort_item_array */
  11731.  
  11732.  
  11733.  
  11734. /*
  11735. disable_zero_counts(item_array)
  11736.  
  11737. You do not want to work on the characters
  11738. that were not in the input file so you
  11739. disable them.
  11740. */
  11741.  
  11742. disable_zero_counts(item_array)
  11743. struct item_struct item_array[];
  11744. {
  11745. int i;
  11746.  
  11747. for(i=0; i<LENGTH; i++){
  11748. if(item_array[i].count == 0)
  11749. item_array[i].indicator = 'D';
  11750. } /* ends loop over i */
  11751. } /* ends disable_zero_counts */
  11752.  
  11753. /*
  11754. combine_and_code_2_smallest_items(item_array, not_ended)
  11755.  
  11756. This function calls other functions to find the two
  11757. smallest items and then combine or link them.
  11758.  
  11759. */
  11760.  
  11761. combine_and_code_2_smallest_items(item_array, not_ended)
  11762. struct item_struct item_array[];
  11763.  
  11764. int *not_ended;
  11765. {
  11766. char r[80];
  11767. int next_smallest, smallest;
  11768.  
  11769.  
  11770. find_smallest_item(item_array, &smallest);
  11771. if(smallest <= 0){
  11772. *not_ended = 0;
  11773. }
  11774.  
  11775. else{
  11776. next_smallest = smallest;
  11777. find_next_smallest_item(item_array, &next_smallest);
  11778. code_2_smallest_items(item_array, smallest, next_smallest);
  11779. combine_2_smallest_items(item_array, smallest,
  11780. next_smallest);
  11781. }
  11782.  
  11783. } /* ends combine_and_code_2_smallest_items */
  11784.  
  11785. /*
  11786. find_smallest_item(item_array, smallest)
  11787.  
  11788.  
  11789. You are working with a sorted item_array
  11790. so you start looking at the bottom of the
  11791. array. You look until you find the first
  11792. 'E'nabled indicator then you stop.
  11793. */
  11794.  
  11795. find_smallest_item(item_array, smallest)
  11796. struct item_struct item_array[];
  11797. int *smallest;
  11798.  
  11799. {
  11800. int i,
  11801. searching;
  11802.  
  11803. *smallest = 0;
  11804. searching = 1;
  11805. i = 255;
  11806.  
  11807. while(searching){
  11808. if(item_array[i].indicator == 'E'){
  11809. *smallest = i;
  11810. searching = 0;
  11811. } /* ends if indicator == 'E' */
  11812.  
  11813. else{
  11814. i = i-1;
  11815. if(i<0){
  11816. *smallest = -1;
  11817. searching = 0;
  11818. }
  11819. } /* ends else indicator != 'E' */
  11820. } /* ends while searching */
  11821. } /* ends find_smallest_item */
  11822.  
  11823.  
  11824. /*
  11825. find_next_smallest_item(item_array, next_smallest)
  11826.  
  11827. You are working with a sorted item_array
  11828. so you start looking at the smallest item of the
  11829. array. You look until you find the first
  11830. 'E'nabled indicator then you stop.
  11831. */
  11832.  
  11833. find_next_smallest_item(item_array, next_smallest)
  11834. struct item_struct item_array[];
  11835. int *next_smallest;
  11836. {
  11837. int i,
  11838. searching;
  11839.  
  11840. searching = 1;
  11841. i = *next_smallest-1;
  11842.  
  11843. while(searching){
  11844. if(item_array[i].indicator == 'E'){
  11845. *next_smallest = i;
  11846. searching = 0;
  11847. } /* ends if indicator == 'E' */
  11848.  
  11849. else{
  11850. i = i-1;
  11851. if(i<0){
  11852. *next_smallest = -1;
  11853. searching = 0;
  11854. }
  11855. } /* ends else indicator != 'E' */
  11856. } /* ends while searching */
  11857. } /* ends find_next_smallest_item */
  11858.  
  11859. /*
  11860. combine_2_smallest_items(...
  11861.  
  11862. . add the two counts together
  11863. . disable the smallest one
  11864. . link the smallest one to the next smallest one
  11865. */
  11866.  
  11867. combine_2_smallest_items(item_array, smallest, next_smallest)
  11868. struct item_struct item_array[];
  11869. int next_smallest, smallest;
  11870. {
  11871. int i, not_finished;
  11872.  
  11873. item_array[next_smallest].count = item_array[smallest].count
  11874. +
  11875.  
  11876. item_array[next_smallest].count;
  11877.  
  11878. item_array[smallest].count = item_array[next_smallest].count;
  11879.  
  11880. item_array[smallest].indicator = 'D';
  11881.  
  11882. i = 0;
  11883.  
  11884. not_finished = 1;
  11885. while(not_finished){
  11886. if(item_array[next_smallest].includes[i] == 256){
  11887. item_array[next_smallest].includes[i] = smallest;
  11888. not_finished = 0;
  11889. }
  11890. else{
  11891. i++;
  11892. if(i > LLENGTH){
  11893. printf("\n\n> Ran out of links\n\n");
  11894.  
  11895. exit(1);
  11896. }
  11897. }
  11898. } /* ends while not_finished */
  11899.  
  11900. } /* ends combine_2_smallest_items */
  11901.  
  11902. /*
  11903. code_2_smallest_items (...
  11904.  
  11905.  
  11906. The smallest item is coded with a ONE
  11907. The next smallest item is coded with a ZERO
  11908. */
  11909.  
  11910.  
  11911. code_2_smallest_items(item_array, smallest, next_smallest)
  11912. struct_item struct item_array[];
  11913. int next_smallest, smallest;
  11914. {
  11915.  
  11916. code_smallest_item(item_array, smallest);
  11917. code_next_smallest_item(item_array, next_smallest);
  11918.  
  11919. } /* code_2_smallest_items */
  11920.  
  11921. /*
  11922. code_smallest_item(item_array, smallest)
  11923.  
  11924. You must code the item as well as
  11925. all the other items included with it
  11926. on down to the end.
  11927.  
  11928. Set the code to ONE.
  11929. */
  11930.  
  11931. code_smallest_item(item_array, smallest)
  11932. struct item_struct item_array[];
  11933. short smallest;
  11934. {
  11935.  
  11936. int i,
  11937. j,
  11938. k,
  11939. setting;
  11940.  
  11941. j = smallest;
  11942. setting = 1;
  11943.  
  11944. i = 0;
  11945.  
  11946. /* set code ONE */
  11947. while(setting){
  11948. if(item_array[j].coded[i] == OTHER) {
  11949. item_array[j].coded[i] = ONE;
  11950. setting = 0;
  11951. } /* ends if == OTHER */
  11952.  
  11953. else
  11954. i++;
  11955. } /* ends while setting */
  11956. /* Recursive calls */
  11957. for(k=0; k<LLENGTH; k++)
  11958. if(item_array[j].includes[k] != 256)
  11959. code_smallest_item(item_array,
  11960. item_array[j].includes[k];
  11961.  
  11962. } /* ends code_smallest_item */
  11963.  
  11964. /*
  11965. code_next_smallest_item(item_array, smallest)
  11966.  
  11967. You must code the item as well as
  11968. all the other items included with it
  11969. on down to the end.
  11970.  
  11971. Set the code to ZERO
  11972. */
  11973.  
  11974. code_next_smallest_item(item_array, smallest)
  11975. struct item_struct item_array[];
  11976. short smallest;
  11977. {
  11978.  
  11979. int i,
  11980. j,
  11981. k,
  11982. setting;
  11983.  
  11984. j = smallest;
  11985. setting = 1;
  11986. i = 0;
  11987.  
  11988. /* set code ZERO */
  11989.  
  11990. while(setting){
  11991. if(item_array[j].coded[i] == OTHER){
  11992. item_array[j].coded[i] = ZERO;
  11993. setting = 0;
  11994. } /* ends if == OTHER */
  11995.  
  11996. else
  11997. i++;
  11998. } /* ends while setting */
  11999.  
  12000. /* Recursive calls */
  12001. for(k=0; k<LLENGTH; k++)
  12002. if(item_array[j].includes[k] != 256)
  12003.  
  12004. code_next_smallest_item(item_array,
  12005. item_array[j].includes[k]);
  12006.  
  12007. } /* ends code_next_smallest_item */
  12008.  
  12009. /*
  12010. reverse_order_of_coded(item_array)
  12011.  
  12012. Now trace backwards
  12013. */
  12014.  
  12015. reverse_order_of_coded(item_array)
  12016. struct item_struct item_array[];
  12017. {
  12018. char temp;
  12019. int i, j;
  12020.  
  12021. for(i=0; i<LENGTH; i++){
  12022. if(item_array[i].coded[0] != OTHER){
  12023. for(j=0; j<(CODE_LENGTH/2); j++){
  12024. temp = item_array[i].coded[j];
  12025. item_array[i].coded[j] =
  12026. item_array[i].coded [CODE_LENGTH-1-j];
  12027. item_array[i].coded[CODE_LENGTH-1-j] = temp;
  12028. } /* ends loop over j */
  12029. } /* ends if coded[0] != OTHER */
  12030. } /* ends loop over i */
  12031. } /* reverse_order_of coded */
  12032.  
  12033. /* End of File */
  12034.  
  12035.  
  12036. Listing 4 (cujhuff3.c)
  12037. /****************************************************
  12038. *
  12039. * file d:\lsu\cujhuff3.c
  12040. *
  12041. ****************************************************/
  12042.  
  12043.  
  12044. #include "d:\lsu\cujhuff.h"
  12045.  
  12046.  
  12047. /*
  12048. code_and_write_output_file(...
  12049.  
  12050. Look at each byte in the input file.
  12051. Code each byte using the item_array.coded.
  12052. When the output buffer (packed bits) is on a byte
  12053. boundary, then write it out to the output file
  12054. */
  12055.  
  12056.  
  12057. code_and_write_output_file(item_array, in_file name,
  12058. out_file_name, file_header)
  12059. char in_file_name[],
  12060. out_file_name[];
  12061. struct item_struct item_array[];
  12062. struct header_struct *file_header;
  12063.  
  12064. {
  12065. char in_buffer[IB_LENGTH],
  12066. out_buffer[OB_LENGTH],
  12067. r[80];
  12068.  
  12069. int coding,
  12070. counter,
  12071. in_file_desc,
  12072. j,
  12073. not_end_of_file,
  12074. out_file_desc;
  12075.  
  12076. long bytes_read,
  12077. bytes_written,
  12078. i,
  12079. in_counter,
  12080. in_file_displacement,
  12081. out_counter;
  12082.  
  12083.  
  12084. open_files(in_file_name, out_file_name,
  12085. &in_file_desc, &out_file_desc);
  12086.  
  12087. lseek(in_file_desc, 0L, 0);
  12088. file_header->in_file-length = lseek(in_file_desc, 0L, 2);
  12089. lseek(in_file_desc, 0L, 0);
  12090.  
  12091. output_file_header(file_header, out_file_desc);
  12092.  
  12093. clear_input_buffer(in_buffer);
  12094. clear_output_buffer(out_buffer);
  12095.  
  12096. in_counter = 0;
  12097. out_counter = 0;
  12098. in_file_displacement = 0;
  12099. not_end_of_file = 1;
  12100. counter = 0;
  12101.  
  12102. while(not_end_of_file){
  12103.  
  12104. position_in_file_displacement(in_file_desc,
  12105. in_file_displacement);
  12106.  
  12107. bytes_read = my_read(in_file_desc, in_buffer, IB_LENGTH);
  12108. /*printf("\n\t\tread %d bytes", bytes_read);*/
  12109.  
  12110. if(bytes_read < IB_LENGTH)
  12111. not_end_of_file = 0;
  12112.  
  12113. i = 0;
  12114. coding = 1;
  12115.  
  12116. while(coding){
  12117.  
  12118. if((counter % 100) == 0)
  12119. printf("\n> Coding - counter=%d\n", counter);
  12120. if((counter % 10) == 0)
  12121. printf(".");
  12122. counter++;
  12123.  
  12124. code_byte(item_array, i, in_buffer, out_buffer,
  12125. &in_counter, &out_counter);
  12126. i++;
  12127. /*printf("\n\n> in_counter=%ld out_counter=%ld\n",
  12128. in_counter, out_counter);*/
  12129.  
  12130. /*************************************
  12131. *
  12132. * The rest of this function looks
  12133. * at the output buffer and writes it
  12134. * out when the buffer is on a byte
  12135. * boundary.
  12136. *
  12137. *************************************/
  12138.  
  12139. if( (out_counter/8 >= 100) &&
  12140. (out_counter % 8 == 0) ){
  12141. printf("\n> Writing to output file");
  12142. /*printf("\n\t> out count = %d", out_counter/8);*/
  12143. write_output(out_file_desc, out_buffer,
  12144.  out_counter/8);
  12145. out_counter = 0;
  12146. clear_output_buffer(out_buffer);
  12147. } /* ends if 100 bytes in out_buffer and on a byte
  12148. boundary */
  12149.  
  12150. if(i == bytes_read){
  12151. in_file_displacement = in_file_displacement + i;
  12152. coding = 0;
  12153. } /* ends if the in_buffer is empty */
  12154.  
  12155. } /* ends while coding */
  12156.  
  12157. } /* ends while not_end_of_file */
  12158.  
  12159. printf("\n> Writing to output file");
  12160. write_output(out_file_desc, out_buffer, out_counter/8);
  12161.  
  12162.  
  12163. close(in_file_desc);
  12164. close(out_file_desc);
  12165.  
  12166. } /* ends code_and write_output_file */
  12167.  
  12168. /*
  12169. open_files(...
  12170.  
  12171. Open the input and output file.
  12172. */
  12173.  
  12174. open_files(in_file_name, out_file_name, in_file_desc,
  12175. out_file_desc)
  12176. char in_file_name[], out_file_name[];
  12177. int *in_file_desc, *out_file_desc;
  12178. {
  12179. int a ,b;
  12180.  
  12181. *out_file_desc = open(out_file_name, 0_RDWR 0_CREAT 0_BINARY,
  12182. S_IWRITE);
  12183.  
  12184. *in_file_desc = open(in_file_name, 0_RDWR 0_CREAT 0_BINARY,
  12185. S_IWRITE);
  12186.  
  12187. } /* ends open_files */
  12188.  
  12189. /*
  12190. output_file_header(...
  12191.  
  12192. This function outputs the short file header
  12193. to the beginning of the compressed file.
  12194.  
  12195. */
  12196.  
  12197.  
  12198. output_file_header(file_header, out_file_desc)
  12199. int out_file_desc;
  12200. struct header_struct *file_header;
  12201. {
  12202. int i;
  12203.  
  12204. char out_buffer[sizeof(struct header_struct)],
  12205. *charptr,
  12206. name[80];
  12207.  
  12208. charptr = (char *)file_header;
  12209. for(i=0; i<((sizeof(struct header_struct))); i++)
  12210. out_buffer[i] = *charptr++;
  12211.  
  12212. write_output(out_file_desc, out_buffer,
  12213. sizeof(struct header_struct));
  12214.  
  12215. } /* ends output_item_array */
  12216.  
  12217.  
  12218. /*
  12219. clear_input_buffer(in_buffer)
  12220.  
  12221. This clears out the input buffer.
  12222. */
  12223.  
  12224. clear_input_buffer(in_buffer)
  12225. char in_buffer[];
  12226. {
  12227. int i;
  12228. for(i=0; i<IB_LENGTH; i++)
  12229. in_buffer[i] = ' ';
  12230. } /* ends clear_in_buffer */
  12231.  
  12232.  
  12233. /*
  12234. clear_output_buffer(out_buffer)
  12235.  
  12236. This clears out the output buffer.
  12237. */
  12238.  
  12239. clear_output_buffer(out_buffer)
  12240. char out_buffer[];
  12241. {
  12242. int i;
  12243.  
  12244. for(i=0; i<0B_LENGTH; i++)
  12245. out_buffer[i] = 0x00;
  12246. } /* ends clear_out_buffer */
  12247.  
  12248.  
  12249. /*
  12250. position_in_file_displacement(...
  12251.  
  12252. This sets the pointer to the input file
  12253. to the desired located specificied by
  12254. in_file_discplacement.
  12255. */
  12256.  
  12257. position_in_file_displacement(in_file_desc, in_file_displacement)
  12258. int in_file_desc;
  12259. long in_file_displacement;
  12260. {
  12261. long position;
  12262. position = lseek(in_file_desc, 0L, 0);
  12263. position = lseek(in_file_desc, in_file_displacement, 0);
  12264.  
  12265. } /* ends position_in_file_displacement */
  12266. /*
  12267. code_byte(...
  12268.  
  12269. This function looks at the input file byte and
  12270. sets the bits in the output buffer.
  12271. */
  12272.  
  12273. code_byte(item_array, byte, in_buffer, out_buffer, in_counter,
  12274. out_counter)
  12275. char in_buffer[], out_buffer[];
  12276. long byte;
  12277. long *in_counter, *out_counter;
  12278. struct item_struct item_array[];
  12279. {
  12280. char out_code[CODE_LENGTH];
  12281. int i;
  12282.  
  12283. find_output_code(item_array, out_code, in_buffer[byte]);
  12284. *in_counter = *in_counter + 1;
  12285.  
  12286. /*****************************************
  12287. *
  12288. * Set the output code to either ONE or
  12289. * ZERO.
  12290. *
  12291. ******************************************/
  12292.  
  12293. for(i=0; i<CODE_LENGTH; i++){
  12294. if(out_code[i] == ONE){
  12295. set_bit_to_1(out_counter, out_buffer);
  12296. *out_counter = *out_counter + 1;
  12297. } /* ends if out_code == ONE */
  12298.  
  12299. if(out_code[i] == ZERO){
  12300. clear_bit_to_0(out_counter, out_buffer);
  12301. *out_counter = *out_counter + 1;
  12302. } /* ends if out_code == ZERO */
  12303.  
  12304. } /* ends loop over i */
  12305. } /* ends code_byte */
  12306.  
  12307.  
  12308. /*
  12309.  
  12310. find_output_code(...
  12311.  
  12312. Search through the item_array to find the correct
  12313. character and its code.
  12314. */
  12315.  
  12316.  
  12317. find_output_code(item_array, out_code, in_character)
  12318. struct item_struct item_array[];
  12319. char out_code[];
  12320. char in_character;
  12321. {
  12322. int i, j, searching;
  12323.  
  12324. i = 0;
  12325. searching = 1;
  12326. while(searching){
  12327. if(item_array[i].character == in_character){
  12328. searching = 0;
  12329. for(j=0; j<CODE_LENGTH; j++){
  12330. out_code[j] = item_array[i].coded[j];
  12331. } /* ends loop over j */
  12332. } /* ends if there is a match */
  12333. else
  12334. i++;
  12335. } /* ends while searching */
  12336. } /* ends find_output_code */
  12337.  
  12338.  
  12339. /*
  12340. set_bit_to_1(out_counter, out_buffer)
  12341.  
  12342. This function sets the specified bit in the output
  12343.  
  12344. buffer to a 1.
  12345. */
  12346.  
  12347.  
  12348. set_bit_to_1(out_counter, out_buffer)
  12349. long *out_counter;
  12350. char out_buffer[];
  12351. {
  12352. int bit_in_byte,
  12353. byte_in_buffer;
  12354. char temp;
  12355. bit_in_byte = *out counter % 8;
  12356. byte_in_buffer = *out_counter / 8;
  12357. switch(bit_in_byte){ 
  12358. case 0:
  12359. temp = out_buffer[byte_in_buffer] SET_BIT_ZERO;
  12360. break;
  12361. case 1:
  12362. temp = out_buffer[byte_in_buffer] SET_BIT_ONE;
  12363.  
  12364. break;
  12365. case 2:
  12366. temp = out_buffer[byte_in_buffer] SET_BIT_TWO;
  12367. break;
  12368. case 3:
  12369. temp = out_buffer[byte_in_buffer] SET_BIT_THREE;
  12370. break;
  12371. case 4:
  12372. temp = out_buffer[byte_in_buffer] SET_BIT_FOUR;
  12373. break;
  12374. case 5:
  12375. temp = out_buffer[byte_in_buffer] SET_BIT_FIVE;
  12376. break;
  12377. case 6:
  12378. temp = out_buffer[byte_in_buffer] SET_BIT_SIX;
  12379. break;
  12380. case 7:
  12381. temp = out_buffer[byte_in_buffer] SET_BIT_SEVEN;
  12382. break;
  12383. } /* ends switch */
  12384. out_buffer[byte_in_buffer] = temp;
  12385. } /* ends set_bit_to_1 */
  12386.  
  12387.  
  12388. /*
  12389. clear_bit_to_0(out_counter, out_buffer)
  12390.  
  12391. This function sets the specified bit in the
  12392. output buffer to 0.
  12393. */
  12394.  
  12395.  
  12396. clear_bit_to_0(out_counter, out_buffer)
  12397. long *out_counter;
  12398. char out_buffer[];
  12399. {
  12400. int bit_in_byte,
  12401. byte_in_buffer;
  12402. char temp;
  12403.  
  12404. bit_in_byte = *out_counter % 8;
  12405. byte_in_buffer = *out_counter / 8;
  12406.  
  12407. switch(bit_in_byte){
  12408. case 0:
  12409. temp = out_buffer[byte_in_buffer] & CLEAR_BIT_ZERO;
  12410. break;
  12411. case 1:
  12412. temp = out_buffer[byte_in_buffer] & CLEAR_BIT_ONE;
  12413. break;
  12414. case 2:
  12415. temp = out_buffer[byte_in_buffer] & CLEAR_BIT_TWO;
  12416. break;
  12417. case 3:
  12418. temp = out_buffer[byte_in_buffer] & CLEAR_BIT_THREE;
  12419. break;
  12420. case 4:
  12421. temp = out_buffer[byte_in_buffer] & CLEAR_BIT_FOUR;
  12422. break;
  12423.  
  12424. case 5:
  12425. temp = out_buffer[byte_in_buffer] & CLEAR_BIT_FIVE;
  12426. break;
  12427. case 6:
  12428. temp = out_buffer[byte_in_buffer] & CLEAR_BIT_SIX;
  12429. break;
  12430. case 7:
  12431. temp = out_buffer[byte_in_buffer] & CLEAR_BIT_SEVEN;
  12432. break;
  12433. } /* ends switch */
  12434. out_buffer[byte_in_buffer] = temp;
  12435. } /* ends clear_bit_to_0 */
  12436.  
  12437. /*
  12438. write_output(...
  12439.  
  12440. This function writes the output buffer to the
  12441. output file.
  12442. */
  12443.  
  12444. write_output(out_file desc, out_buffer, number_of_bytes)
  12445. char out_buffer[];
  12446. int out_file_desc;
  12447. long number_of_bytes;
  12448. {
  12449. int bytes_written;
  12450.  
  12451. bytes_written = write(out_file_desc, out_buffer,
  12452. number_of_bytes);
  12453. /*printf("\n> wrote %d bytes", bytes_written);*/
  12454.  
  12455. } /* ends write_output */
  12456.  
  12457. /* End of File */
  12458.  
  12459.  
  12460. Listing 5 (cujhuff4.c)
  12461. /****************************************************
  12462. *
  12463. * file d:\lsu\cujhuff4.c
  12464. *
  12465. ***************************************************/
  12466.  
  12467. #include "d:\lsu\cujhuff.h"
  12468.  
  12469. /*
  12470. decode_compressed_file(...
  12471.  
  12472. This is the main function of the decompression
  12473. portion of the program.
  12474.  
  12475. */
  12476.  
  12477. decode_compressed_file (input_file_name, output_file_name,
  12478. item_array, file_header)
  12479. char input_file_name[], output_file_name[];
  12480. struct item_struct item_array[];
  12481. struct header_struct *file_header;
  12482. {
  12483.  
  12484. char r[80];
  12485. int in_file_desc,
  12486. out_file_desc;
  12487.  
  12488. open_files_for_decode(input_file_name, output_file_name,
  12489. &in_file_desc, &out_file_desc};
  12490.  
  12491. input_file_header(file_header, in_file_desc);
  12492. convert_short_to_long(item_array, file_header);
  12493.  
  12494. decode_file_and_write_output (item_array,
  12495. in_file_desc,
  12496. out_file_desc);
  12497.  
  12498. close_decode_files(in_file_desc, out_file_desc);
  12499.  
  12500. } /* ends decode_compressed_file */
  12501.  
  12502. /*
  12503. open_files_for_decode(...
  12504.  
  12505. Open the input end output file.
  12506. */
  12507.  
  12508. open_files_for_decode(in_file_name, out_file_name,
  12509. in_file_desc, out_file_desc)
  12510. char in_file_name[], out_file_name[],
  12511. int *in_file_desc, *out_file_desc;
  12512. {
  12513. int a ,b;
  12514.  
  12515. *out_file_desc = open(out_file_name, 0_RDWR 0_CREAT 0_BINARY,
  12516. S_IWRITE);
  12517. *in_file_desc = open(in_file_name, 0_RDWR 0_CREAT 0_BINARY,
  12518. S_IWRITE);
  12519.  
  12520. } /* ends open_files_for_decode */
  12521.  
  12522. /*
  12523. input_file_header( file_header, in_file_desc)
  12524.  
  12525. This function reads in the short header from the
  12526. front of the input file.
  12527. */
  12528.  
  12529.  
  12530. input_file_header( file_header, in_file_desc)
  12531. int in_file_desc;
  12532. struct header_struct *file_header;
  12533. {
  12534. int bytes_read,
  12535. i;
  12536.  
  12537. char in_buffer[(sizeof(struct header_struct))],
  12538. *charptr,
  12539. name[80];
  12540.  
  12541. long position;
  12542.  
  12543.  
  12544.  
  12545. position = lseek(in_file_desc, 0L, 0);
  12546. bytes_read = my_read(in_file_desc, in_buffer,
  12547. sizeof(struct header_struct));
  12548. /*printf("\n\n> Read %d bytes using file header", bytes_read);*/
  12549.  
  12550. charptr = (char *)file_header;
  12551. for(i=0; i<((sizeof(struct header_struct))); i++)
  12552. *charptr++ = in_buffer[i];
  12553.  
  12554. } /* ends input_item_array */
  12555.  
  12556. /*
  12557.  
  12558. decode_file_and write_output(...
  12559.  
  12560. This function takes in the packed bits, decodes them,
  12561. and puts the decoded characters out to the output
  12562. file.
  12563.  
  12564. /*
  12565.  
  12566. decode_file_and_write_output(item_array, in_file_desc, out_file_desc)
  12567. int in_file_desc, out_file_desc;
  12568. struct item_struct item_array[];
  12569. {
  12570. char output_buffer[IB_LENGTH],
  12571. r[80],
  12572. stream_buffer[CODE_LENGTH],
  12573. stream_of_bits[OB_LENGTH];
  12574.  
  12575. int bytes_read,
  12576. counter
  12577. i,
  12578. in_disp,
  12579. not_finished,
  12580. output_pointer,
  12581. stream_pointer;
  12582. counter = 0;
  12583. output_pointer = 0;
  12584. stream_pointer = 0;
  12585. bytes_read = 0;
  12586. not_finished = 1;
  12587.  
  12588. for(i=0; i<IB_LENGTH; i++)
  12589. output_buffer[i] = OTHER;
  12590.  
  12591. while(not_finished){
  12592.  
  12593. read_in_buffer(in_file_desc, in_disp,
  12594. stream_of_bits, &bytes_read);
  12595.  
  12596. /*printf("\n> read %d bytes into stream of bits", bytes_read);*/
  12597. stream_pointer = 0;
  12598.  
  12599. if(bytes_read < OB_LENGTH)
  12600. not_finished = 0;
  12601.  
  12602. /* work through the stream_of_bits
  12603.  
  12604. you're finished when the stream_pointer
  12605. equals bytes_read * 8 bits per byte. */
  12606.  
  12607. while(stream_pointer < bytes_read*8){
  12608. convert_bits_to_char(stream_of_bits, stream_pointer,
  12609. stream_buffer);
  12610.  
  12611. decode_bits(stream_buffer, item_array,
  12612. output_buffer, &output_pointer,
  12613. &stream_pointer);
  12614.  
  12615. /*printf("\n> stream pointer = %d", stream_pointer);*/
  12616. /* if output_buffer fills up write it to disk */
  12617. if(output_pointer >= IB_LENGHT-10){
  12618. counter++;
  12619. printf("\n> Writing to output file-%d", counter);
  12620. write_out_buffer(out_file_desc,
  12621. output_buffer,
  12622. output_pointer);
  12623. output_pointer = 0;
  12624. } /* ends if output pointer is too big */
  12625. } /* ends while stream_pointer < bytes_read*8 */
  12626.  
  12627. } /* ends while not_finished */
  12628.  
  12629. if(output_pointer > 0) {
  12630. printf ("\n> Writing to output file");
  12631. write_out_buffer(out_file_desc, output_buffer, output_pointer);
  12632. }
  12633.  
  12634. } /* ends decode_file_and_write_output */
  12635.  
  12636.  
  12637. /*
  12638. read_in_buffer(...
  12639.  
  12640. This function reads the input file and puts the packed
  12641. bits into the stream_of_bits.
  12642. */
  12643.  
  12644. read_in_buffer(in_file_desc, in_disp, stream_of_bits,
  12645. bytes_read)
  12646. int *bytes_read, in_file_desc, in_disp;
  12647. char stream_of_bits[];
  12648. {
  12649. int b;
  12650.  
  12651. for(b=0; b<OB_LENGTH; b++)
  12652. stream_of_bits[b] = 0x00;
  12653.  
  12654. *bytes_read = read(in_file_desc, stream_of_bits, OB_LENGTH);
  12655.  
  12656. } /* ends read_in_buffer */
  12657.  
  12658.  
  12659. /*
  12660. convert_bits_to_char(...
  12661.  
  12662. This function takes the next CODE_LENGTH # of bits
  12663.  
  12664. from the stream_of_bits and converts them to bytes
  12665. having the value ONE or ZERO. These bytes are placed
  12666. into the stream_buffer. This makes it easier for
  12667. the decode bits function to compare coded with the
  12668. packed bits.
  12669. */
  12670.  
  12671.  
  12672. convert_bits_to_char(stream_of_bits, stream_pointer, stream_buffer)
  12673. char stream_of_bits[], stream_buffer[];
  12674. int stream_pointer;
  12675. {
  12676. char temp;
  12677.  
  12678. int bit_in_byte,
  12679. byte_in_buffer,
  12680. i,
  12681. j;
  12682.  
  12683. for(i=0; i<CODE_LENGTH; i++)
  12684. stream_buffer[i] = OTHER;
  12685.  
  12686. j = -1:
  12687.  
  12688. for(i=stream_pointer; i<stream_pointer+CODE_LENGTH; i++){
  12689.  
  12690. j++;
  12691. bit_in_byte = i % 8;
  12692. byte_in_buffer = i/8;
  12693.  
  12694. /* Test the bit by shifting it to the left
  12695. by the number bit_in_byte and then ANDing
  12696. it with 1000 0000 (128).
  12697. For this routine bit zero is the left most
  12698. or most significant bit of temp */
  12699.  
  12700. temp = stream_of_bits[byte_in buffer];
  12701. temp = temp << bit_in_byte;
  12702. if((temp & SET_BIT_ZERO) != 0)
  12703. stream_buffer[j] = ONE;
  12704. else
  12705. stream_buffer[j] = ZERO;
  12706.  
  12707. } /* ends loop over i */
  12708.  
  12709. } /* ends convert_bits_to_char */
  12710.  
  12711. /*
  12712. decode_bits(...
  12713.  
  12714. Look at the converted bits from the packed file
  12715. compare them to the item_array.coded (start looking
  12716. at the top of the item_array since those occur most
  12717. frequently) and put the original character in the
  12718. output buffer
  12719. */
  12720.  
  12721. decode_bits(stream_buffer, item_array,
  12722. output_buffer, output_pointer,
  12723.  
  12724. stream_pointer)
  12725. char output_buffer[], stream_buffer[];
  12726. int *output_pointer, *stream_pointer;
  12727. struct item_struct item_array[];
  12728. {
  12729. int found,
  12730. j,
  12731. i,
  12732. length_of_code,
  12733. not_finished;
  12734.  
  12735. char compare_string[CODE_LENGTH], r[80];
  12736.  
  12737. found = 0;
  12738. not_finished = 1;
  12739. j = 0;
  12740.  
  12741. /* search through the item_array looking for a coded array that
  12742. matches the characters in the stream_buffer
  12743. If you find it, increase the stream_pointer by
  12744. adding length_of_code to it. */
  12745.  
  12746. while(not_finished){
  12747.  
  12748. extract_code(item_array[j].coded, compare_string, &length_of_code);
  12749.  
  12750. if(strncmp (compare_string,
  12751. stream_buffer,
  12752. length_of_code) == 0){
  12753. found = 1;
  12754. not_finished = 0;
  12755. *stream_pointer = *stream_pointer + length_of_code;
  12756. } /* ends if strncmp */
  12757.  
  12758. else{ /* did not find the code so keep looking */
  12759. j++;
  12760. if(j > LENGTH){
  12761. not_finished = 0;
  12762. printf("\n\n\t> FAILURE - did not find code\n\n");
  12763. }
  12764. } /* ends else */
  12765.  
  12766. } /* ends while not_finished */
  12767.  
  12768. /* now put the decoded character in to the output_buffer */
  12769.  
  12770. if(found == 1){
  12771. output_buffer[*output_pointer] = item_array[j].character;
  12772. *output_pointer = *output_pointer + 1;
  12773. } /* ends if found == 1 */
  12774.  
  12775. } /* ends decode_bits */
  12776.  
  12777. /*
  12778. extract_code(...
  12779.  
  12780. Pull the code out of item_array[x].coded,
  12781. put it into compare_string, and put the
  12782. length of the code into length.
  12783.  
  12784. */
  12785.  
  12786. extract_code(coded, compare_string, length_of_code}
  12787. char coded[], compare_string[];
  12788. int *length_of_code;
  12789. {
  12790. int i, j, k, m, not_finished;
  12791.  
  12792. /* clear the compare string */
  12793. for(k=0; k<CODE LENGTH; k++)
  12794. compare_string[k] = OTHER;
  12795.  
  12796. not_finished = 1;
  12797. *length_of_code = 0;
  12798. i = CODE_LENGTH-1;
  12799. j = 0;
  12800.  
  12801. /* Start at the end of coded and search back until you
  12802. run out of code i.e. you hit a OTHER. */
  12803.  
  12804. while(not_finished){
  12805. if(coded[i] != OTHER){
  12806. i-;
  12807. j++;
  12808. }
  12809. else
  12810. not_finished = 0;
  12811. } /* ends while not_finished */
  12812.  
  12813. /* adjust value of i */
  12814. i++;
  12815.  
  12816. /* Copy the coded to the compare_string */
  12817. *length_of_code = j;
  12818. m = 0;
  12819. for(k=i; k<CODE_LENGTH; k++){
  12820. compare_string[m] = coded[k];
  12821. m++;
  12822. }
  12823.  
  12824. } /* ends extract_code */
  12825.  
  12826. /*
  12827. write_out_buffer(...
  12828.  
  12829. Write the output_buffer to file.
  12830. */
  12831.  
  12832. write_out_buffer(out_file_desc, output_buffer, output_pointer)
  12833. char output_buffer[];
  12834. int out_file_desc, output_pointer;
  12835. {
  12836. int bytes_written;
  12837.  
  12838. bytes_written =
  12839. my_write(out_file_desc, output_buffer, output_pointer);
  12840. } /* ends write_out_buffer */
  12841.  
  12842.  
  12843.  
  12844. /*
  12845. close_decode_files(...
  12846.  
  12847. Close the files.
  12848. */
  12849.  
  12850. close_decode_files(in_file_desc, out_file_desc)
  12851. int in_file_desc, out_file_desc;
  12852. {
  12853.  
  12854. close(in_file_desc);
  12855. close (out_file_desc);
  12856.  
  12857. } /* ends close_decode_files */
  12858.  
  12859. /* End of File */
  12860.  
  12861.  
  12862.  
  12863.  
  12864.  
  12865.  
  12866.  
  12867.  
  12868.  
  12869.  
  12870.  
  12871.  
  12872.  
  12873.  
  12874.  
  12875.  
  12876.  
  12877.  
  12878.  
  12879.  
  12880.  
  12881.  
  12882.  
  12883.  
  12884.  
  12885.  
  12886.  
  12887.  
  12888.  
  12889.  
  12890.  
  12891.  
  12892.  
  12893.  
  12894.  
  12895.  
  12896.  
  12897.  
  12898.  
  12899.  
  12900.  
  12901.  
  12902.  
  12903.  
  12904.  
  12905.  
  12906.  
  12907. Arithmetic In Factorial-Base
  12908.  
  12909.  
  12910. Frederick W. Hegeman
  12911.  
  12912.  
  12913. Frederick Hegeman is an amateur programmer and computer language hobbyist. He
  12914. can be reached at P.O. Box 2368, Rapid City, SD 57709, telephone (605)
  12915. 343-7014.
  12916.  
  12917.  
  12918. C takes a machine-oriented approach to numbers; its arithmetic types are
  12919. coupled to the physical architecture of the processor. Arithmetic operations
  12920. on ints, longs, and doubles use a compact representation and provide fast and
  12921. well-defined performance. The cost is limits on the magnitude and accuracy of
  12922. operands and results.
  12923. Other languages take a less mechanical approach. Common Lisp supports bignums,
  12924. integral values limited only by available memory, and ratios, rational values
  12925. which allow exact computations by storing separate integral valued numerators
  12926. and denominators (which can be bignums, of course).
  12927. For C applications that need to operate beyond the restraints imposed by
  12928. float.h and limits.h, similar types could be coded, but doing so would be a
  12929. nightmare. What kind of code would be required to compare
  12930. (4129181559/4294967296) to (449/467)? To multiply or divide them? Even worse,
  12931. memory usage could grow without limit, up to the point where the program
  12932. fails.
  12933. Happily, there is a more practical approach that works within bounded memory
  12934. and almost allows computation to unlimited magnitude and perfect accuracy.
  12935.  
  12936.  
  12937. Factorial-Base Arithmetic
  12938.  
  12939.  
  12940. Any rational number can be represented exactly in a number system based, not
  12941. on increasing powers of a particular number (e.g., 100,101, 102, ...), but on
  12942. the series of factorial numbers, (1!, 2!, 3!, ... ). Any integer in any
  12943. static-base system can also be represented in factorial-base.
  12944. The decimal integer 231 can be represented as 2x102+3x101+1x100 or as
  12945. 1x5!+4x4!+2x3!+1x2!+1x1!. And any rational fraction has an exact
  12946. representation in factorial-base, even if infinitely repeating in a
  12947. static-base.
  12948. Click Here for Equation
  12949. Whatever numbers a computer manipulates must be rational numbers. If I type in
  12950. 3.1415926535897932384, I am not typing in p, but a rational approximation. I
  12951. can't calculate the square root of 2; I can calculate a rational number than
  12952. approximates it. Every operand that can be used, and every result that can be
  12953. returned, is a rational number. I find it ironic that Georg Cantor, who
  12954. discovered those computer incompatible values, the transfinite number, was
  12955. also responsible for factorial-base representation. In Cantor's scheme every
  12956. number that can be computed by machine can, given the resources, be computed
  12957. exactly.
  12958. The limiting resources of the factorial-base system are memory and the
  12959. implementation language. The language is the most important factor. Time, in
  12960. theory, is not a factor. Memory is the limiting factor only for very small
  12961. systems such as micro controllers. In C, the limiting factor is related to the
  12962. integer square root of LONG_MAX. Because ANSI conforming compilers are allowed
  12963. generous amounts of slack, the limiting value may be one less than the
  12964. quotient of INT_MAX divided by sizeof(long). For a subset without longs, the
  12965. limits are set by the integer square root of INT_MAX.
  12966.  
  12967.  
  12968. Representing Factorial-Base Numbers In C
  12969.  
  12970.  
  12971. Factorial-base numbers have a natural representation in C as arrays of some
  12972. signed integral type. Consider the representation of decimal 231 given
  12973. previously. The value of each factorial place is an integer to be stored in a
  12974. separate element of the array. Arrange things so that references begin at
  12975. array[1], rather than at array[0], and the subscripts track the factorial
  12976. places
  12977. int factorial_integer[6] = {0,1,1,2,4,1};
  12978. Likewise for the ratio 1/3
  12979. int factorial_fraction[4] = {0,0,0,2};
  12980. To represent the real number 231 + 1/3, some scheme is needed to keep track of
  12981. both parts. Declaring a struct with members to hold the separate arrays does
  12982. so in a straightforward manner. However, since many subset compilers, and
  12983. cross compilers, do not implement structs and since the idea of a tiny
  12984. controller limited to eight-bit "words" calculating to the limits of 10! and
  12985. 1/10! has a certain charm, I chose to implement both parts in a single array,
  12986. as illustrated in Figure 1.
  12987. The numbers are represented in a sign and magnitude scheme. The equivalent of
  12988. 2's complement simply does not exist and since the subscript 0 is free, it
  12989. holds the sign. The factorial-base integer follows. Each element of the
  12990. integer portion will hold a maximum value equal to its subscript. If it ever
  12991. happens that the value exceeds the subscript, the number can be rearranged so
  12992. that it does not. Consider array element 2, which represents 2!. The maximum
  12993. entry it should hold is 2. The value of 2 at subscript 2 is 2*2!. Suppose the
  12994. entry at element 2 was 3 -- the value of 3 at subscript 2 would be 3*2!. That
  12995. is the definition of 3!, and the number can be normalized by representing it
  12996. in terms of 3!. Note that it is not necessary to actually calculate the value
  12997. of 3!.
  12998. Consider that the subscript i references the ith factorial "digit". What is
  12999. the maximum value for i? Suppose the number is represented as an array of
  13000. ints. The element i may have to temporarily contain the value of (i+1)*i
  13001. before being normalized. This value must be representable as a positive int,
  13002. and must not be allowed to suddenly turn negative, in order to propagate a
  13003. carry value into the guard, G, and mark integer overflow. Since we are using
  13004. ints, (i+1)*i cannot exceed INT_MAX. Assume INT_MAX is 32767 and the maximum
  13005. value of i is 180, or 1 less than the integer square root of INT_MAX.
  13006. The rest of the array contains the factorial-base fraction. Consider it as a
  13007. separate array beginning at &array[G]. The value of factorial place 1, at
  13008. subscript G+1, is some integer x/1!, which is the same as x*1!, which is the
  13009. same as x. This subscript is referenced to propagate carrys and borrows from
  13010. the fraction part to the integer part during normalization. Otherwise, the
  13011. array value at subscript G+1 is 0.
  13012. The remaining array elements represent the fraction proper. Each element will
  13013. hold a maximum value equal to 1 less than its subscript relative to G. The
  13014. maximum subscript (relative to G) is 1 less than the integer square root of
  13015. INT_MAX. Underflow, or loss of significance, spills into subscript G2.
  13016. Now, assume the number is represented an array of longs and LONG_MAX is
  13017. 2147483647. Most applications will not use up 46,339 factorial places. ANSI
  13018. conforming compilers need only provide objects, including arrays, of up to
  13019. 32,767 bytes and since each array element uses 4 bytes, arrays of at least
  13020. 8,191 longs are presumably available.
  13021.  
  13022.  
  13023. facbase.c
  13024.  
  13025.  
  13026. Procedures and declarations for factorial-base numbers, the operations +, --,
  13027. /, *, conversion to and from ASCII decimal, and support functions, are in
  13028. Listing 1. Since that code contains only simple macros and no structs, unions,
  13029. longs, or pointers to anything more exotic than chars and ints, it should
  13030. compile under even the most rudimentary systems.
  13031. Only two things might lead to trouble on a system. First, some subset
  13032. compilers may not allow compile-time initialization of arrays and variables.
  13033. In that case, a short routine will have to be written to do runtime
  13034. initialization of the integer variable nowarning and the constant arrays
  13035. zero[] and one[]. The second problem may be lack of stack space. Some of the
  13036. routines can use prodigious amounts of stack. On systems that allot a fixed
  13037. amount of memory for use as a program stack, it may be necessary to declare
  13038. all or most of the temporary numbers as static arrays, rather than the default
  13039. to auto.
  13040. As described above, either integer or fractional parts can be adjusted freely
  13041. up to about 180 factorial digits. The system is written for 100 factorial
  13042. digits in both integer and fractional parts for the simple reason that the
  13043. values of factorials in that range can be checked against the tables in the
  13044. Chemical Rubber Co. Standard Mathematical Tables.
  13045. If some of the code looks oddly familiar, it is because operations on
  13046. factorial-base numbers built around arrays are almost exactly the same as
  13047. binary operations on arrays of bits. Shifting left and right are different
  13048. because they are done by overt multiplication and division, while binary
  13049. operations shift left and right in order to multiply and divide quickly. The
  13050. only routine that might seem totally unfamiliar is the normalization routine
  13051. that cleans up after operations. The software must mimic normalization usually
  13052. performed in silicon.
  13053. In the absence of overflow or underflow, mathematic operations in
  13054. factorial-base are exact. No rounding or truncation need ever be performed
  13055. except when converting to a static-base on output. This code converts to
  13056. decimal. To the limits of the system, the maximum error of any computation, no
  13057. matter how large or how small the result, need never be greater than Â± .5
  13058. units in the last place displayed. The actual results can always be exact and
  13059. no errors propagate through a series of calculations. (Rounding to nearest, as
  13060. that implies, might not be as appropriate as rounding towards zero or always
  13061. rounding towards Â± x. Note that the code provided truncates, rather than
  13062. rounds.)
  13063. Included at the end of Listing 1 is code for a simple and slow RPN calculator
  13064. program, which compiles when DEBUG is #defined. The calculator includes a
  13065. routine to display a factorial-base number in factorial-base. Factorial-base
  13066. integers are well-behaved but factorial-base fractions are confusing unless
  13067. viewed as separate digits. Consider the infinite series for e: [IMG =
  13068. 9202F1QY.PCX] The series can be coded directly into an array, normalized, and
  13069. printed out. If the fraction is 100 factorial digits, it will print out
  13070. correctly to 159 decimal places. On the other hand, .000 000 000 000 000 000
  13071. 000 000 1 will underflow; it only has an exact representation in 105 factorial
  13072. digits. Note, though, how it prints in decimal after underflow. If the
  13073. fraction is 180 factorial digits, the value of e prints out correctly to 333
  13074. decimal places, 1.0E--44 requires all 180 factorial digits and 1.0E--45
  13075. underflows.
  13076.  
  13077.  
  13078. Conclusion
  13079.  
  13080.  
  13081. The factorial-base arithmetic is not practical for recalculation of a
  13082. spreadsheet. But, for applications where accuracy is more important that
  13083. speed, factorial-base arithmetic may be the answer. For programs that need to
  13084. deal in exact fractions, factorial-base arithmetic is probably a good choice.
  13085. Suppose you want both fast and accurate arithmetic. You have what you think
  13086. are fast and simple algorithms that perform calculations correct to 20 decimal
  13087. places. Or do they? How do you verify that? How do you debug your code? Don't
  13088. dismiss factorial-base math out of hand as too slow for practical
  13089. applications.
  13090.  
  13091.  
  13092.  
  13093. Bibliography
  13094.  
  13095.  
  13096. Wayner, Peter. "Error-Free Fractions," BYTE, Vol. 13, No. 6, June, 1988, pp.
  13097. 289-298. Presents a limited implementation in Pascal and cites a submission to
  13098. CACM that has not yet appeared.
  13099. The Chemical Rubber Co., Standard Mathematical Tables, 18th ed., Cleveland,
  13100. OH, 1970. Later editions have slimmed down, but earlier ones like this contain
  13101. exact values for factorials up to 20!
  13102. Wozniak, Stephen. "The Impossible Dream: Computing e to 116,000 Places with a
  13103. Personal Computer," BYTE, Vol. 6, No. 6, June, 1981, Pp. 392-407. Where else
  13104. can you find the value of e printed out to 1,500 decimal places?
  13105. Figure 1 A Number in Factorial-Base Expressed as an Array
  13106.  <-- integer part -> <- fraction part ->
  13107.  +----+----+----+ +----+----+----+----+ +----+----+
  13108.  1! 2! .. i! 1! 2! .. f! 
  13109.  +----+----+----+ +----+----+----+----+ +----+----+
  13110.  0 1 2 i G G+1 G+2 G+f G2
  13111.  [0] The sign.
  13112.  [1] An integer portion of i factorial-base digits.
  13113.  The value of [n] ranges from 0 to n, The value
  13114.  [i] of the integer portion is ?*1! + ?*2! + ... + ?*i!
  13115.  [G] A guard to detect integer overflow.
  13116.  [G+1] Records carrys & borrows during normalization.
  13117.  [G+2] A fractional portion of f-1 factorial-base digits.
  13118.  The value of [n] ranges from 0 to n-1. The value
  13119.  [G+f] of the fraction is ?/2! + ?/3! + ... + ?/f!
  13120.  [G2] A guard to detect fractional underflow.
  13121.  
  13122. Listing 1 (facbase.c) Arithmetic in Factorial-Base
  13123. #include <stdio.h>
  13124.  
  13125. #ifndef TRUE
  13126. #define TRUE 1
  13127. #endif
  13128. #ifndef FALSE
  13129. #define FALSE 0
  13130. #endif
  13131.  
  13132. #define DEBUG TRUE /* include RPN calculator */
  13133. #define PLUS FALSE /* sign values */
  13134. #define MINUS TRUE
  13135. #define MAXFBINTEGER 100 /* <= 180 for arrays of ints */
  13136. #define MAXFBFRACTION 100 /* <= 180 for arrays of ints */
  13137. #define HIGHGUARD MAXFBINTEGER + 1
  13138. #define LOWGUARD MAXFBFRACTION + 1
  13139. #define ARRAYSIZE 1 + HIGHGUARD + LOWGUARD
  13140.  
  13141. /* two constants in the proper format */
  13142. int zero[ARRAYSIZE] = { PLUS, 0 }; /* signed 0 */
  13143. int one[ARRAYSIZE] = { PLUS, 1, 0 };
  13144.  
  13145. /* a flag set by divide() while "estimating" */
  13146. int nowarning = FALSE;
  13147. /* assign the value of one array to a second array */
  13148. assignto(dest, source)
  13149. int *dest;
  13150. int *source;
  13151. {
  13152. int i;
  13153.  
  13154. for(i = 0; i < ARRAYSIZE; i++)
  13155. dest[i] = source[i];
  13156. {
  13157.  
  13158. /* z = abs(x) */
  13159. absolute(x, z)
  13160. int *x;
  13161. int *z;
  13162. {
  13163. if(z != x) /* if not changing in place */
  13164. assignto(z, x); /* copy */
  13165. z[0] = PLUS; /* force positive */
  13166. }
  13167.  
  13168. /* Assign unary negative of x to z */
  13169. negative(x, z)
  13170. int *x;
  13171. int *z;
  13172. {
  13173. int sign;
  13174.  
  13175. sign = (x[0] == PLUS) ? MINUS : PLUS;
  13176. if(z != x) /*if not changing in place */
  13177. assignto(z, x); /* copy */
  13178. z[0] = sign; /* change sign */
  13179. }
  13180.  
  13181. /* compare 2 factorial-base numbers for x > y */
  13182. int greaterthan(x, y)
  13183. int *x;
  13184. int *y;
  13185. {
  13186. int i, j;
  13187.  
  13188. /*
  13189. * check the integer part first,
  13190. * largest subscript to smallest
  13191. */
  13192. for(i = MAXFBINTEGER; (x[i] == y[i]) && (i > 1); -i)
  13193. ;
  13194. if(i != 0)
  13195. {
  13196. if(x[i] > y[i])
  13197. return(TRUE);
  13198. if(x[i] < y[i])
  13199. return(FALSE);
  13200. }
  13201. /*
  13202. * The integer portions are equal,
  13203. * continue with the fractional part,
  13204. * smallest subscript to largest.
  13205. */
  13206. for(i = HIGHGUARD + 1, j = 1;
  13207. (x[i] == y[i]) && (j < MAXFBFRACTION - 1);
  13208. i++, j++)
  13209. ;
  13210. return((x[i] > y[i]) ? TRUE : FALSE);
  13211. }
  13212.  
  13213. /* Compare 2 factorial-base numbers for x < y */
  13214. int lessthan(x, y)
  13215. int *x;
  13216. int *y;
  13217.  
  13218. {
  13219. int i, j;
  13220.  
  13221. /*
  13222. * check the integer part first,
  13223. * largest subscript to smallest
  13224. */
  13225. for(i = MAXFBINTEGER; (x[i] == y[i]) && (i > 1); -i)
  13226. ;
  13227. if(i != 0)
  13228. {
  13229. if(x[i] < y[i])
  13230. return (TRUE);
  13231. if(x[i] > y[i])
  13232. return(FALSE);
  13233. }
  13234. /*
  13235. * The integer portions are equal,
  13236. * continue with the fractional part,
  13237. * smallest subscript to largest.
  13238. */
  13239. for(i = HIGHGUARD + 1, j = 1;
  13240. (x[i] == y[i]) && (j < MAXFBFRACTION - 1);
  13241. i++, j++)
  13242. ;
  13243. return((x[i] < y[i]) ? TRUE : FALSE);
  13244. }
  13245.  
  13246. /* Compare 2 factorial-base numbers for x == y */
  13247. int equalto(x, y)
  13248. int *x;
  13249. int *y;
  13250. {
  13251. int i;
  13252.  
  13253. /*
  13254. * Check the whole array except the sign
  13255. * - order doesn't matter.
  13256. */
  13257. for(i = ARRAYSIZE-1; (x[i] == y[i]) && (i > 1); -i)
  13258. ;
  13259. return((x[i] == y[i]) ? TRUE : FALSE);
  13260. }
  13261.  
  13262. /*
  13263. * "Normalize" a factorial-base number. All of the
  13264. * arithmetic functions call this routine to handle
  13265. * carrys and borrows. A factorial-base number has a
  13266. * proper form where every factorial position in its
  13267. * integer part has a value between 0 and the the
  13268. * magnitude of its position and every factorial position
  13269. * in its fractional part has a value between 0 and one
  13270. * less than the magnitude of its position.
  13271. */
  13272. normalize(n)
  13273. int *n;
  13274. {
  13275. int i, j;
  13276. int *x;
  13277.  
  13278.  
  13279. x = &n[HIGHGUARD];
  13280. /*
  13281. * First, check for loss of precision
  13282. * during multiplication or division.
  13283. */
  13284. if(x[LOWGUARD])
  13285. {
  13286. if(nowarning != TRUE)
  13287. fputs("UNDERFLOW\n", stderr);
  13288. x[LOWGUARD] = 0;
  13289. }
  13290. /*
  13291. * Now, work the fractional part first,
  13292. * largest subscript to smallest.
  13293. * The subscript j is the factorial position
  13294. * being put into proper form.
  13295. */
  13296. for(j = MAXFBFRACTION, i = j - 1; i >= 1; -i, -j)
  13297. {
  13298. /* if(x[j] >= j) carry */
  13299. x[i] += (x[j] / j);
  13300. x[j] %=j;
  13301. if(x[j] < 0) /* borrow */
  13302. {
  13303. x[i] -= 1;
  13304. /* modulo= j */
  13305. x[j] += j; /* make positive */
  13306. }
  13307. }
  13308. /* shift any carry to integer part & clear carry */
  13309. n[1] += x[1];
  13310. x[1] = 0;
  13311. /*
  13312. * Now, normalize the integer part,
  13313. * working from smallest subscript to largest.
  13314. * The subscript i is the factorial position
  13315. * being put into proper form.
  13316. */
  13317. x = n;
  13318. for(i = 1, j = 2; i <= MAXFBINTEGER; i++, j++)
  13319. {
  13320. /* if(x[i] >= j) carry */
  13321. x[j] += (x[i] / j);
  13322. x[i] %= j;
  13323. if(x[i] < 0) /* borrow */
  13324. {
  13325. x[j] -= 1;
  13326. x[i] += j;
  13327. }
  13328. }
  13329. if(x[i]) /* if an entry in x[HIGHGUARD] */
  13330. {
  13331. fputs("OVERFLOW\n", stderr);
  13332. x[i] = 0;
  13333. }
  13334. }
  13335.  
  13336. /* Add y to x, put result in z */
  13337.  
  13338. add(x, y, z)
  13339. int *x;
  13340. int *y;
  13341. int *z;
  13342. {
  13343. int sign, i;
  13344. int copy[ARRAYSIZE];
  13345.  
  13346. if(x[0] != y[0]) /* if different signs */
  13347. {
  13348. if(y[0] == MINUS)
  13349. {
  13350. /*
  13351. * Change the sign of y
  13352. * and subtract y from x.
  13353. */
  13354. negative(y, copy);
  13355. subtract(x, copy, z);
  13356. }
  13357. else
  13358. {
  13359. /*
  13360. * Change the sign of x
  13361. * and subtract x from y.
  13362. */
  13363. negative(x, copy);
  13364. subtract(y, copy, z);
  13365. }
  13366. }
  13367. else
  13368. {
  13369. sign = x[0]; /* save the sign */
  13370. for(i = ARRAYSIZE - 1; i > 0; -i)
  13371. z[i] = x[i] + y[i];
  13372. z[0] = sign;
  13373. normalize(z);
  13374. }
  13375. }
  13376.  
  13377. /* Subtract y from x, put result in z */
  13378. subtract(x, y, z)
  13379. int *x;
  13380. int *y;
  13381. int *z;
  13382. {
  13383. int sign, i;
  13384. int copy[ARRAYSIZE];
  13385.  
  13386. if(x[0] != y[0]) /* if signs are different */
  13387. {
  13388. negative(y, copy); /* change sign of y */
  13389. sign = x[0]; /* save sign of x */
  13390. add(x, copy, z);
  13391. z[0] = sign;
  13392. return;
  13393. }
  13394. else if(y[0] == MINUS) /* (-x) - (-y) */
  13395. {
  13396. /* if(abs(y) < abs(x)) sign = MINUS */
  13397.  
  13398. sign = lessthan(y, x);
  13399. }
  13400. else
  13401. {
  13402. /* if(x < y) sign = MINUS */
  13403. sign = lessthan(x, y);
  13404. }
  13405. /* Subtract based on the absolute values */
  13406. if (lessthan(x, y))
  13407. {
  13408. for(i = ARRAYSIZE - 1; i > 0; -i)
  13409. z[i] = y[i] - x[i];
  13410. }
  13411. else
  13412. {
  13413. for(i = ARRAYSIZE - 1; i > 0; -i)
  13414. z[i] = x[i] - y[i];
  13415. }
  13416. z[0] = sign;
  13417. normalize(z);
  13418. }
  13419.  
  13420. /*
  13421. * Multiply factorial-base x by integer y, result in z.
  13422. * Utility routine called by multiply(), divide(),
  13423. * atofact(), fractoa(), and facttoa(), and always
  13424. * with a positive value for y
  13425. */
  13426. multfbyi(x, y, z)
  13427. int *x;
  13428. int y;
  13429. int *z;
  13430. {
  13431. int i;
  13432.  
  13433. for(i = ARRAYSIZE - 1; i > 0; -i)
  13434. z[i] = x[i] * y;
  13435. normalize(z);
  13436. }
  13437.  
  13438. /*
  13439. * Divide factorial-base x by integer y, result in z.
  13440. * Utility routine called by multiply(), divide(),
  13441. * and facttoa(), and always with a positive y
  13442. */
  13443. divfbyi(x, y, z)
  13444. int *x;
  13445. int y;
  13446. int *z;
  13447. {
  13448. int i, j, carry, part;
  13449.  
  13450. carry = 0;
  13451. /*
  13452. * Work the integer part first,
  13453. * from the largest subscript to smallest
  13454. */
  13455. for(i = MAXFBINTEGER, j = i + 1; i >= 1; -i, -j)
  13456. {
  13457.  
  13458. part = x[i] + carry * j;
  13459. carry = part % y;
  13460. z[i] = part / y;
  13461. }
  13462. /*
  13463. * Now, work the fractional part,
  13464. * from the smallest subscript to largest
  13465. */
  13466. for(i = HIGHGUARD + 1, j = 1;
  13467. j <= MAXFBFRACTION; i++, j++)
  13468. {
  13469. part = x[i] + carry * j;
  13470. carry = part % y;
  13471. z[i] = part / y;
  13472. }
  13473. /*
  13474. * propogate any carry into z[LOWGUARD]
  13475. * to mark underflow and loss of precision
  13476. */
  13477. z[i] = carry * j;
  13478. normalize(z);
  13479. }
  13480.  
  13481. /*
  13482. * Multiply factorial-base x by factorial-base y,
  13483. * assign result to z. Uses the identity
  13484. * number * (integer + fraction)
  13485. * == (number * integer) + (number * fraction)
  13486. */
  13487. multiply(x, y, z)
  13488. int *x;
  13489. int *y;
  13490. int *z;
  13491. {
  13492. int i, j, k, sign;
  13493. int partial [ARRAYSIZE];
  13494. int temp[ARRAYSIZE];
  13495. int copy[ARRAYSIZE];
  13496.  
  13497. if(x[0] != y[0]) /* if signs different */
  13498. sign = MINUS;
  13499. else
  13500. sign = PLUS;
  13501.  
  13502. assignto(partial, zero); /* Initialize result */
  13503. /*
  13504. * Work the integer portion first,
  13505. * from smallest subscript to largest
  13506. */
  13507. absolute(x, copy); /* copy = abs(x) */
  13508. /* first, find largest subscript k where y[k] != 0 */
  13509. for(k = MAXFBINTEGER; (k > 0) && (y[k] == 0); -k)
  13510. ;
  13511. for(i = 1; i <= k; i++)
  13512. {
  13513. /* first shift copy by factorial position */
  13514. multfbyi(copy, i, copy);
  13515. if(y[i]) /* don't bother multiplying by 0 */
  13516. {
  13517.  
  13518. /* multiply by factorial digit */
  13519. multfbyi(copy, y[i], temp);
  13520. add(partial, temp, partial);
  13521. }
  13522. }
  13523.  
  13524. /* now work fraction part */
  13525. assignto(copy, x); /* reset copy */
  13526. /* find largest subscript k where y[k] != 0 */
  13527. for(k = ARRAYSIZE - 1; (k > HIGHGUARD + 1) && (y[k] == 0); -k)
  13528. ;
  13529. for(i = HIGHGUARD + 2, j = 2; i <= k; i++, j++)
  13530. {
  13531. /* first shift copy by factorial position */
  13532. divfbyi(copy, j, copy);
  13533. if(y[i]) /* don't bother multiplying by zero */
  13534. {
  13535. /* multiply by factorial digit */
  13536. multfbyi(copy, y, temp);
  13537. add(partial, temp, partial);
  13538. }
  13539. }
  13540. partial[0] = sign;
  13541. assignto(z, partial);
  13542. }
  13543. /*
  13544. * Divide factorial-base x by factorial-base y, store
  13545. * result in z. Uses blackboard style long division.
  13546. */
  13547. divide(x, y, z)
  13548. int *x;
  13549. int *y;
  13550. int *z;
  13551. {
  13552. int i, j, sign;
  13553. int estimate;
  13554. int copyx[ARRAYSIZE];
  13555. int copyy[ARRAYSIZE];
  13556. int temp[ARRAYSIZE];
  13557. int partial[ARRAYSIZE];
  13558.  
  13559. if(x[0] != y[0]) /* if signs different */
  13560. sign = MINUS;
  13561. else
  13562. sign = PLUS;
  13563. absolute(x, copyx);
  13564. absolute(y, copyy);
  13565. assignto(partial, copyx);
  13566. assignto(temp, copyy);
  13567. /*
  13568. * First, estimate the integer part of result by
  13569. * driving y to 1.xxx. Division is VERY slow, so
  13570. * the extra time spent to identify special cases
  13571. * is well worth it.
  13572. */
  13573. if(equalto(temp, zero))
  13574. {
  13575. /* division by zero fault */
  13576. /* not handled here */
  13577.  
  13578. return;
  13579. }
  13580. else if(lessthan(partial, temp))
  13581. {
  13582. assignto(partial, zero); /* integer part 0 */
  13583. }
  13584. else if(lessthan(one, temp))
  13585. {
  13586. /*
  13587. * This could cause a spurious UNDERFLOW
  13588. * message even though the final result
  13589. * would be exact, so we set a flag to
  13590. * suppress the warning.
  13591. */
  13592. nowarning = TRUE;
  13593. while(lessthan(one, temp))
  13594. {
  13595. divfbyi(partial, 2, partial);
  13596. divfbyi(temp, 2, temp);
  13597. }
  13598. multfbyi(partial, 2, partial);
  13599. nowarning = FALSE; /* reset flag */
  13600. }
  13601. else if(lessthan(temp, one))
  13602. {
  13603. while(lessthan(temp, one))
  13604. {
  13605. multfbyi(partial, 2, partial);
  13606. multfbyi(temp, 2, temp);
  13607. }
  13608. }
  13609. else /* division by 1 or -1 */
  13610. {
  13611. assignto(z, x);
  13612. z[0] = sign;
  13613. return;
  13614. }
  13615. /* Now, delete fractional part of estimate */
  13616. for(i = HIGHGUARD + 1; i < ARRAYSIZE; i++)
  13617. partial[i] = 0;
  13618. multiply(copyy, partial, temp);
  13619. while(greaterthan(temp, copyx))
  13620. {
  13621. subtract(partial, one partial);
  13622. subtract(temp, copyy, temp);
  13623. }
  13624. subtract(copyx, temp, copyx);
  13625. multfbyi(copyx, 2, copyx);
  13626. /* partial now holds integer part of result */
  13627.  
  13628. /*
  13629. * Now, proceed by long division to divide by
  13630. * the fractional part - using the subscript
  13631. * (less 1) as the estimate at each position
  13632. */
  13633. for(i = HIGHGUARD + 2, j = 2;
  13634. !equalto(copyx, zero) && (i < ARRAYSIZE);
  13635. i++)
  13636. {
  13637.  
  13638. estimate = j - 1;
  13639. do {
  13640. multfbyi(copyy, estimate-, temp);
  13641. } while(greaterthan(temp, copyx));
  13642. subtract(copyx, temp, copyx);
  13643. partial[i] = ++estimate;
  13644. multfbyi(copyx, ++j, copyx);
  13645. }
  13646. normalize(partial);
  13647. if(sign == MINUS)
  13648. partial[0] = MINUS;
  13649. assignto(z, partial);
  13650. }
  13651.  
  13652. /*
  13653. * ASCII to factorial-base conversion
  13654. * Just like ASCII to binary conversion!
  13655. */
  13656. atofact(s, z)
  13657. char s[];
  13658. int *z;
  13659. }
  13660. int i, j, sign;
  13661. int ipart[ARRAYSIZE];
  13662. int fpart[ARRAYSIZE];
  13663.  
  13664. assignto(ipart, zero);
  13665. assignto(fpart, zero);
  13666. i = 0;
  13667. sign = PLUS;
  13668. while(s[i] == ' ' s[i] == '\t')
  13669. i++;
  13670. if(s[i] == '-' s[i] == '+')
  13671. {
  13672. if(s[i++] == '-' )
  13673. sign = MINUS;
  13674. }
  13675. for(; s[i] >= '0' && s[i] <= '9'; i++)
  13676. {
  13677. multfbyi(ipart, 10, ipart);
  13678. ipart[1] = s[i] - '0';
  13679. normalize(ipart);
  13680. }
  13681. if(s[i] == '.')
  13682. {
  13683. i++;
  13684. j = 0;
  13685. for( ; s[i] >= '0' && s[i] <= '9'; i++)
  13686. {
  13687. multfbyi(fpart, 10, fpart);
  13688. ++j;
  13689. fpart[1] = s[i] - '0';
  13690. normalize(fpart);
  13691. }
  13692. while(j-)
  13693. divfbyi(fpart, 10, fpart);
  13694. add(ipart, fpart, ipart);
  13695. }
  13696. ipart[0] = sign;
  13697.  
  13698. assignto(z, ipart);
  13699. }
  13700.  
  13701. /*
  13702. * Convert the fractional part of x to ASCII.
  13703. * Up to count characters go to stdout.
  13704. */
  13705. fractoa(x, count)
  13706. int *x;
  13707. int count;
  13708. {
  13709. int i;
  13710. int temp[ARRAYSIZE];
  13711.  
  13712. assignto(temp, x);
  13713. for(i = 1; i <= MAXFBINTEGER; i++)
  13714. temp[i] = 0; /* erase integer part */
  13715. temp[0] = PLUS; /* always positive */
  13716. if(equalto(temp, zero))
  13717. return; /* no fractional part to print out */
  13718. putchar('.');
  13719. while(count- && !equalto(temp, zero))
  13720. {
  13721. multfbyi(temp, 10, temp);
  13722. putchar('0' + 6 * temp[3] + 2 * temp[2] + temp[1]);
  13723. /* Now erase the integer part */
  13724. temp[3] = temp[2] = temp[1] = 0;
  13725. }
  13726. }
  13727.  
  13728. /*
  13729. * CAUTION - altering the size of outbuff requires
  13730. * some art. If MAXFBINTEGER == 100, it must be
  13731. * large enough to hold the 160 decimal digit integer
  13732. * 9.4259 * 10^159. If MAXFBINTEGER == 180, it must
  13733. * be large enough for the 332 decimal digit integer
  13734. * 3.6362 * 10^331. If you want to deal with really
  13735. * big numbers and increase MAXFBINTEGER, you'll have
  13736. * to give some thought as to how large the conversion
  13737. * buffer is going to have to be.
  13738. */
  13739. /* Allow a little slack */
  13740. char outbuff[MAXFBINTEGER*2] = { 0 };
  13741. int outptr; /* Actually an index and not a "ptr" */
  13742.  
  13743. /*
  13744. * Factorial-base to ASCII conversion, integer
  13745. * part end up to count characters of fractional
  13746. * portion go to stdout.
  13747. */
  13748. facttoa(x, count)
  13749. int *x;
  13750. int count;
  13751. {
  13752. int i, j, sign;
  13753. int temp[ARRAYSIZE];
  13754. int val[ARRAYSIZE];
  13755.  
  13756. outptr = 0;
  13757.  
  13758. assignto(val, zero);
  13759. assignto(temp, x);
  13760. if((sign = temp[0]) == MINUS)
  13761. {
  13762. temp[0] = PLUS;
  13763. putchar('-');
  13764. }
  13765. for(i = ARRAYSIZE - 1; i > HIGHGUARD; -i)
  13766. temp[i] = 0; /* erase fractional part */
  13767. while(!equalto(temp, zero))
  13768. {
  13769. divbyi(temp, 10, temp);
  13770. for(i = HIGHGUARD + 2, j = 1; j <= 4; i++, j++)
  13771. {
  13772. val[i] = temp[i];
  13773. }
  13774. multfbyi(val, 10, val);
  13775. outbuff[outptr++] =
  13776. '0' + 6 * val[3] + 2 * val[2] + val[1];
  13777. val[3] = val[2] = val[1] = 0;
  13778. /*Now erase fractional portion of temp */
  13779. temp[i-1] = temp[i-2] = temp[i-3] = temp[i-4] = 0;
  13780. }
  13781. if (outptr == 0) /* if no integer part */
  13782. putchar ('0');
  13783. else
  13784. {
  13785. while(outptr-)
  13786. putchar(outbuff[outptr]);
  13787. }
  13788. fractoa(x, count); /* to print fractional portion */
  13789. putchar('\n');
  13790. }
  13791.  
  13792. */ Remainder of file is RPN calculator & display */
  13793. #ifdef DEBUG
  13794. /*
  13795. * Print a factorial-base number in factorial-base.
  13796. * "digits" are printed between '<' and '>',
  13797. * output goes to stdout.
  13798. */
  13799. facprint(x)
  13800. int *x;
  13801. {
  13802. int i, j;
  13803. if(x[0] == MINUS)
  13804. printf("-");
  13805. /* Delete any leading zeroes */
  13806. for(i = MAXFBINTEGER; i >= 1; i-)
  13807. {
  13808. if(x[i] != 0)
  13809. break;
  13810. }
  13811. /* Print any integer portion */
  13812. for( ; i >= 2; )
  13813. printf("<%d>", x[i-]);
  13814. /* Make sure to print at least one digit */
  13815. printf("<%d>", x[1]);
  13816. printf(".");
  13817.  
  13818. /*
  13819. * Print fractional part, deleting any trailing
  13820. * zeroes but printing at least one digit
  13821. */
  13822. i = HIGHGUARD + 2; /* start at 2! */
  13823. printf("<%d>", x[i++]);
  13824. for(j = 0; i < ARRAYSIZE; i++)
  13825. {
  13826. if(x[i] == 0)
  13827. j += 1;
  13828. else
  13829. {
  13830. while(j)
  13831. {
  13832. printf("<0>");
  13833. -j;
  13834. }
  13835. printf("<%d>", x[i]);
  13836. }
  13837. }
  13838. printf("\n");
  13839. }
  13840.  
  13841. /*
  13842. * A simple but VERY slow reverse Polish calculator.
  13843. * Commands +, -, *, /, D to print in decimal,
  13844. * F to print in factorial, C to clear the stack,
  13845. * S to display the whole stack in decimal,
  13846. * a decimal number to use as an operand,
  13847. * only 1 operator or operand per line!!
  13848. */
  13849.  
  13850. #define IPSIZE 256
  13851. char input[IPSIZE];
  13852. #define STACKSIZE 8
  13853. int stack[STACKSIZE][ARRAYSIZE];
  13854.  
  13855. main(argc, argv)
  13856. int argc;
  13857. char *argv[];
  13858. {
  13859. int x, i, prompt, depth;
  13860.  
  13861. prompt = (argc > 1) ? TRUE : FALSE;
  13862. depth = 0;
  13863. for( ; ; )
  13864. {
  13865. if(prompt)
  13866. printf(%d> ", depth);
  13867. if(fgets(input, IPSIZE, stdin) == NULL)
  13868. break;
  13869. switch(x = input[0])
  13870. {
  13871. case 'c': /* clear the stack */
  13872. case 'C': depth = 0;
  13873. continue;
  13874. case 'f': /* print top of stack in factorial */
  13875. case 'F': if(depth < 1)
  13876. {
  13877.  
  13878. printf("empty stack\n");
  13879. continue;
  13880. }
  13881. printf("%d: ", depth - 1);
  13882. facprint(&stack[depth-1][0]);
  13883. continue;
  13884. case'd': /* print top of stack in decimal */
  13885. case 'D': if(depth < 1)
  13886. {
  13887. printf("empty stack\n");
  13888. continue;
  13889. }
  13890. printf("%d: ", depth - 1);
  13891. facttoa(&stack[depth-1] [0], 50);
  13892. continue;
  13893. case's': /* display contents of stack */
  13894. case 'S': if(depth < 1)
  13895. {
  13896. printf("empty stack\n");
  13897. continue;
  13898. {
  13899. for(i = 0; i < depth; i++)
  13900. }
  13901. printf("%d: ", i);
  13902. facttoa(&stack[i][0], 50);
  13903. }
  13904. continue;
  13905. case '+': if(depth < 2)
  13906. }
  13907. printf("stack will underflow\n");
  13908. continue;
  13909. {
  13910. add (&stack [depth-2] [0],
  13911. &stack [depth- 1] [0],
  13912. &stack[depth-2] [0] );
  13913. -depth;
  13914. continue;
  13915. case '/': if(depth < 2)
  13916. {
  13917. printf ("stack will underflow\n");
  13918. continue;
  13919. }
  13920. if (equal to(&stack [depth-1][0], zero))
  13921. {
  13922. printf ("division by zero\n");
  13923. /* allow the 0 to be discarded */
  13924. }
  13925. else
  13926. {
  13927. divide (&stack [depth-2][0],
  13928. &stack [depth-1][0],
  13929. &stack[depth-2][0]);
  13930. }
  13931. -depth;
  13932. continue;
  13933. case '*': if(depth < 2)
  13934. {
  13935. printf ("stack will underflow\n");
  13936. continue;
  13937.  
  13938. }
  13939. multiply(&stack [depth-2][0],
  13940. &stack [depth-1] [0],
  13941. &stack[depth-2] [0]);
  13942. -depth;
  13943. continue;
  13944. case '-': if(input[1] == '\n')
  13945. {
  13946. if(depth < 2)
  13947. {
  13948. printf(
  13949. "stack will underflow\n");
  13950. continue;
  13951. }
  13952. subtract(&stack[depth-2][0],
  13953. &stack[depth-1][0],
  13954. &stack(depth-2][0]);
  13955. -depth;
  13956. continue;
  13957. } /* else a negative number */
  13958. else
  13959. break;
  13960. default: if(x != '-' && x != '.' &&
  13961. (x < 'o' x > '9'))
  13962. {
  13963. printf("invalid entry\n");
  13964. continue;
  13965. }
  13966. break;
  13967. /* to convert and stack a number */
  13968. }
  13969. if(depth >= STACKSIZE)
  13970. {
  13971. printf("stack will overflow\n");
  13972. continue;
  13973. }
  13974. atofact(input, &stack[depth++][0]);
  13975. continue;
  13976. }
  13977. }
  13978. #endif
  13979.  
  13980. /* End of File */
  13981.  
  13982.  
  13983.  
  13984.  
  13985.  
  13986.  
  13987.  
  13988.  
  13989.  
  13990.  
  13991.  
  13992.  
  13993.  
  13994.  
  13995.  
  13996.  
  13997.  
  13998.  
  13999.  
  14000.  
  14001. Wildcard Subdirectory Searches
  14002.  
  14003.  
  14004. Toby Popenfoose
  14005.  
  14006.  
  14007. Toby Popenfoose is a system analyst developing image processing applications
  14008. for a large commercial printer. Prior to that, he was an Air Force Instructor
  14009. Pilot. Toby has a B.S.E.E. from Purdue University and a M.S.C.S from
  14010. Midwestern State University. You may contact him at 5720 East 200 North,
  14011. Warsaw, IN 46580.
  14012.  
  14013.  
  14014. I have often been asked "How many C source modules do we have in our Image
  14015. Processing system?", or "Is there an easy way to delete all of the *.SAV files
  14016. in all 40 subdirectories?". These two questions motivated me to develop a
  14017. general-purpose wildcard subdirectory search utility function.
  14018. I had several design goals. First, I wanted to make the function general
  14019. enough to put in my utility library. Second, I wanted to maintain wildcard
  14020. search capability. I wanted the function to perform a subdirectory search
  14021. based on a switch. In addition, I wanted to minimize machine dependencies so I
  14022. could port it to my AMIGA workstation. Last, I wanted the function to provide
  14023. a totals switch.
  14024.  
  14025.  
  14026. Design
  14027.  
  14028.  
  14029. Looking at the available functions in Microsoft C v5.1, I decided to use
  14030. _dos_findfirst and _dos_findnext functions, because they support wildcard
  14031. searches. They also fill in a file info block (find_t structure) with the
  14032. matching file's name. The drawback with this choice is that these functions
  14033. are machine dependent. Two other non-portable machine dependent functions that
  14034. I used are _splitpath() and _makepath(). Note, in Listing 1 the four machine
  14035. dependent functions that I have used begin with an underscore. The ANSI
  14036. standard reserves underscores for secret names with external linkage [1]. Now
  14037. these really are not secret names or they would not have been documented in
  14038. [2]. Looking at my other target platform, the AMIGA, I had two choices. I
  14039. could use the AMIGA_DOS built-in functions Examine and ExNext which would
  14040. require a directory Lock call at each subdirectory level (since the AMIGA is a
  14041. true multitasking workstation) or I could use the well-respected public domain
  14042. resident library ARP.LIBRARY (see sidebar, [3], [4], [5], [6] and [7]) with
  14043. FindFirst and FindNext. I chose the latter because it would also give me
  14044. wildcard capability, perform vertical subdirectory searches, and it more
  14045. closely mimicked the Microsoft MS-DOS _dos_findfirst and _dos_findnext.
  14046. I designed my utility using a recursive main that returns an int value to
  14047. itself. I did this to prove that a main can be recursive because it is a
  14048. function and behaves as any other function would [8]. The pseudo code follows.
  14049. if (subdirectory switch)
  14050. {
  14051. push all subdirectory names onto a FIFO;
  14052. while(subdirectories left)
  14053. {
  14054. get subdirectory off FIFO;
  14055. recursively search that subdirectory;
  14056. }
  14057. }
  14058.  
  14059. find wildcard matches within this directory;
  14060. call user function with path argument
  14061. My first pseudo code was with a stack for the subdirectory names, but I have
  14062. actually implemented a FIFO (first in first out) buffer. If the subdirectories
  14063. have been sorted is some order, that order will be maintained as it
  14064. recursively searches each subdirectory.
  14065. For the switches, I elected to use argv[2] with /S/T. /S is for subdirectory
  14066. searches to be included and /T is for totals to be printed. For output, my
  14067. main routine would call an external function subfunc(char *path) with a path
  14068. pointer as an argument. I decided a maximum of 127 subdirectories per
  14069. directory would be the upper limit of the stack size.
  14070. With some trial and error, I have molded the code in Listing 1. After the
  14071. first attempt at coding this, I realized that recursive functions must use the
  14072. heap (malloc) and not the stack (auto variables) for array storage. A static
  14073. array will not work for this application because a new array is needed at each
  14074. recursive function call entry. For example, in Listing 1 after the main I have
  14075. declared char *path and then malloced out space with path = malloc(_MAX_PATH);
  14076. this uses the heap for my path array data storage. My first attempt used the
  14077. stack with a declaration such as char path[_MAX_PATH].
  14078. The first line of Listing 1 declares main to return an int. You may be
  14079. surprised to see main() return something other than void. This int returned
  14080. keeps track of the total as it transverses the subdirectories.
  14081. The code fragment shown in Listing 2 deserves a more detailed explanation.
  14082. Both _dos_findfirst and _dos_findnext return a 0 if a match is found. My if
  14083. statements use the logical negation of the return value. If there is a match,
  14084. it will evaluate to TRUE. The _dos_findfirst needs the _A_SUBDIR flag argument
  14085. to include subdiretory names in its search. If a match is found the file info
  14086. block (fib) is filed in. This structure is defined in DOS.H. Next, I check to
  14087. make sure the subdirectory is not one of the two special MS-DOS subdirectories
  14088. . or .. and that the matched name is in fact a subdirectory. To see if it is a
  14089. subdirectory name, I look at the file info block attribute and test it for
  14090. being a subdirectory. The bitwise & has a higher precedence then the logical
  14091. && so it requires no parentheses. If I do have a valid subdirectory, its name
  14092. is pushed into the FIFO buffer. This continues until I have no more directory
  14093. entries or until I have no more room in my FIFO subdirectory buffer. Next, I
  14094. terminate the FIFO with a NULL and reset my FIFO pointer to the start of the
  14095. FIFO.
  14096. Now while there is more subdirectories, I pop one out and recursively search
  14097. it with the
  14098. total += main(argc, argv);
  14099. call.
  14100.  
  14101.  
  14102. Alternative Implementations
  14103.  
  14104.  
  14105. I have also implemented this recursive main as a recursive function int
  14106. recurs(). It takes as arguments, a character string for the wildcarded file
  14107. names to match and two Boolean flags (see Listing 6). These flags are for
  14108. subdirectories to be included in the search and/or totals to be printed out,
  14109. which allows the command line argument logic to be contained in a void main().
  14110. A more elegant approach to command line arguments would be a variation of [9].
  14111. I have developed and tested Listing 1, Listing 3, Listing 4, and Listing 6 on
  14112. a PC clone with an INTEL 386 CPU and IBM-DOS v3.30 using Microsoft C v5.1.
  14113. Listing 3, Listing 4, and Listing 5 have developed and tested on an AMIGA 1000
  14114. with a Motorola 68000 and AMIGA-DOS v1.3 using MANX AZTEC C v5.0.
  14115. The only trouble porting was due to non-ANSI functions that were
  14116. machine/operating system/compiler dependent.
  14117. Here are a few examples of how I have incorporated this wildcard subdirectory
  14118. search: a carriage return, line feed corrector for porting ASCII source to and
  14119. from IBM-DOS. I have also used Listing 1 in a zaptime utility to zero the time
  14120. stamp on existing files. I have modifed Listing 1 to remove directories that
  14121. are empty. I have used Listing 6 to modify the SGREP source supplied from [10]
  14122. along with adding the feature of the output being to a file with a ~ prepended
  14123. to the extension. This allowed me to convert over 700 C source files in over
  14124. 33 subdirectories in less than 15 minutes. From the following two line matches
  14125. #include <proto.h>
  14126. #include "proto.h"
  14127. to
  14128. #include <libproto.h>
  14129. #include "locproto.h"
  14130. which was much faster than the time it took to put the prototype includes in
  14131. the first time.
  14132.  
  14133.  
  14134. Bibliography
  14135.  
  14136.  
  14137.  
  14138. [1] Plauger, P. J. "Library Ground Rules". The C Users Journal, August 1990.
  14139. [2] Microsoft C 5.1. Run-Time Library Reference, 1987.
  14140. [3] Manx Software Systems. Aztec C Reference Manual Version 5.0, 1989.
  14141. [4] Commodore Business Machines. AMIGA ROM Kernal Reference Manual: Exec,
  14142. Addison-Wesley, 1986.
  14143. [5] Berry, John Thomas. Inside the AMIGA with C, Howard W. Sams, 1988.
  14144. [6] Peck, Robert A. PROGRAMMER's Guide To The AMIGA, SYBEX, 1987.
  14145. [7] Anderson, Rhett and Randy Thompson. "MAPPING the AMIGA," Compute!, 1990.
  14146. [8] Kernighan, Brian W. and Dennis M. Ritchie. The C Programming Language,
  14147. Prentice Hall, 1978.
  14148. [9] Colner, Don. "An Object-Oriented Approach to Command Line Options." The C
  14149. Users Journal, July 1990.
  14150. [10] C USERS GROUP disk #236, Highly Portable Utilities (CUG Starter Disk).
  14151. Resident Libraries
  14152. Resident libraries are a dynamic mechanism facilitated by the AMIGA-DOS
  14153. Executive. These libraries are linked at runtime contrary to the normal
  14154. compile, link cycle that links a runtime library object module into a binary
  14155. executable at link time. This runtime linking is not truly a link but rather a
  14156. jumptable that jumps to a relative offset from the libraries' base pointer.
  14157. The base pointer is returned from the OpenLibrary() call.
  14158.  
  14159.  
  14160. Advantages
  14161.  
  14162.  
  14163. There are three big advantages to a resident library:
  14164. 1. Using resident libraries, you can reduce the size of executable files
  14165. because the object code necessary for a particular function is contained in
  14166. the resident library. Also, when more than one task is operating in memory
  14167. there is not duplicated code taking up precious memory.
  14168. 2. It takes less time to load an executable because part of the executable is
  14169. already in RAM memory. When there are several tasks multitasking, the
  14170. AMIGA-DOS Executive keeps a list of libraries. Subsequent tasks that try to
  14171. access a particular library that is already on the list are speeded up by the
  14172. executive looking at the list and seeing that it is already resident.
  14173. 3. There is true runtime versioning control on the library themselves. The
  14174. OpenLibrary(char *LibName, long Version) system call allows programs at
  14175. runtime to specify a version number that the present library must be equal to
  14176. or greater than to have a successful open. If versioning is not required, a
  14177. version of 0 as an argument to the OpenLibrary call will allow whatever
  14178. version is present.
  14179.  
  14180.  
  14181. Typical AMIGA DOS 1.3 LIBS
  14182.  
  14183.  
  14184. arp.library is obtainable through most BBS's such as BIX, COMPUSERVE and
  14185. PEOPLES LINK. It falls into the "freeware" category since it is copyrighted by
  14186. ARP Authors (represented by Microsmith's Inc. It contains a multitude of
  14187. functions that allow a programmer and/or user more capability then the
  14188. corresponding AMIGA DOS 1.3 library functions might.)
  14189. Table 1
  14190. Library Name Variable Name Base Address Contents
  14191. -------------------------------------------------------------------------
  14192. clist.libary ClistBase Character string routines
  14193. diskfont.library DiskFontBase Disk-based font routines
  14194. exec.library ExecBase All Exec functions
  14195. dos.library DosBase DOS functions
  14196. graphics.library GfxBase Graphics functions
  14197. icon.library IconBase Workbench functions
  14198. intuition.library IntuitionBase Intuition interfacing
  14199. layers.library LayersBase Window/Layers functions
  14200. mathffp.library MathBase Basic math functions
  14201. mathtrans.library MathTransBase Trancendental math functions
  14202. mathieeedoubbas.library MathIeeeDoubBasBase Double precision IEEE
  14203. timer.library TimerBase Timer funtions
  14204. translator.library TranslatorBase Voice synthesis functions
  14205. arp.library ArpBase DOS functions
  14206.  
  14207. Listing 1
  14208. /*
  14209. A recursive descent main to search directories and subs
  14210. with wildcard and path capabilities -> then pass the
  14211. path name to a user supplied function called "subfunc".
  14212. 2 switches are presently supported: /t for totals and
  14213. /s for subdirectories.
  14214. */
  14215.  
  14216. #include <stdio.h>
  14217. #include <stdlib.h>
  14218. #include <malloc.h>
  14219. #include <string.h>
  14220.  
  14221. #include <dos.h>
  14222.  
  14223. #define MAX_SUB 128 /* max subdirectory width for stack */
  14224. /* subfunc is user module to link to */
  14225. extern void subfunc(char *path);
  14226.  
  14227. int main(int argc, char **argv)
  14228. {
  14229. struct find_t *fib;
  14230. char *path, *drive, *dir, *fname, *ext;
  14231. char *tmppath, *tmpdir, *tmpargv, *subfifo;
  14232. char (*subfifoptr)[_MAX_FNAME];
  14233. int total = 0;
  14234. /* malloc so we don't blow up the stack */
  14235. if (!((fib = malloc(sizeof(struct find_t))) &&
  14236. (path = malloc(_MAX_PATH)) &&
  14237. (drive = malloc(_MAX_DRIVE)) &&
  14238. (dir = malloc(_MAX_DIR)) &&
  14239. (fname = malloc(_MAX_FNAME)) &&
  14240. (ext = malloc(_MAX_EXT)) &&
  14241. (tmpdir = malloc(_MAX_DIR)) &&
  14242. (tmppath = malloc(_MAX_PATH))))
  14243. { /* return resources to DOS */
  14244. free(fib), free(path), free(drive), free(dir);
  14245. free(fname), free(ext), free(tmpdir), free(tmppath);
  14246. printf("NOT ABLE TO MALLOC SPACE!\N");
  14247. exit(-1);
  14248. }
  14249.  
  14250. if (argc < 2)
  14251. { /* there was no command line argument */
  14252. _splitpath(argv[0], drive, dir, fname, ext);
  14253. printf("\n\t%s <filespec> [/s/t]\n", fname);
  14254. printf("\tFilespec can have DOS wildcards!\n");
  14255. printf("\t/S switch includes subdirectories.\n");
  14256. printf("\t/T switch gives a total.\n");
  14257. }
  14258. else
  14259. {
  14260. if (argc > 2 && (strstr(strupr(argv[2]), "/S")))
  14261. { /* if S switch - do subdirectories first */
  14262. tmpargv = argv[1]; /* save argv[1] for future use */
  14263. if (!(subfifo = *subfifoptr =
  14264. (char *)malloc(_MAX_FNAME * MAX_SUB)))
  14265. { /* return resources to DOS */
  14266. free(fib), free(path), free(drive), free(dir);
  14267. free(fname), free(ext), free(tmpdir), free(tmppath);
  14268. printf("UNABLE TO MALLOC FOR INTERNAL FIFO!\N");
  14269. exit(-1);
  14270. }
  14271. _splitpath(argv[1], drive, dir, fname, ext);
  14272. _makepath(path, drive, dir, "*", "*");
  14273. if (!_dos_findfirst(path, _A_SUBDIR, fib))
  14274. do
  14275. {
  14276. if (fib->name[0] != '.' &&
  14277.  fib->attrib & _A_SUBDIR)
  14278.  /* push on FIFO */
  14279. strcpy(*subfifoptr++, fib->name);
  14280.  
  14281.  
  14282. } while (!_dos_findnext(fib) &&
  14283. *subfifoptr <
  14284. &subfifo[(_MAX_FNAME - 1) * MAX_SUB]);
  14285. **subfifoptr = NULL; /* terminate FIFO */
  14286. *subfifoptr = subfifo; /* reset FIFO pointer */
  14287. while(**subfifoptr) /* while not at the end */
  14288. {
  14289. strcpy(tmpdir, dir);
  14290. _makepath(path, drive,
  14291. strcat(tmpdir, *subfifoptr++), fname, ext);
  14292. argv[1] = path;
  14293.  /* recursive part of program */
  14294. total += main(argc, argv); /* check next level */
  14295. }
  14296. free(subfifo);
  14297. argv[1] = tmpargv; /* restore argv[1] */
  14298. }
  14299. /* look for files */
  14300. if (!_dos_findfirst(argv[1], _A_NORMAL, fib))
  14301. do
  14302. {
  14303. _splitpath(argv[1], drive, dir, fname, ext);
  14304. strcpy(tmppath, drive);
  14305. strcat(tmppath, dir);
  14306.  /* now call the work function */
  14307. subfunc(strcat(tmppath, fib->name));
  14308. total++;
  14309. } while (!_dos-findnext(fib));
  14310. if (argc > 2 && strstr(strupr(argv[2]), "/T"))
  14311. printf("\ntotal = %d\t%s files\n", total, argv[1]);
  14312. }
  14313. /* return resources to DOS */
  14314. free(fib), free(path), free(drive), free(dir);
  14315. free(fname), free(ext), free(tmpdir), free(tmppath);
  14316.  
  14317. return total;
  14318. }
  14319. /* End of File */
  14320.  
  14321.  
  14322. Listing 2
  14323. if (!_dos_findfirst(path, _A_SUBDIR, fib))
  14324. do {
  14325. if (fib->name[O] != '.' &&
  14326. fib->attrib & _A_SUBDIR)
  14327. strcpy(*subfifoptr++, fib->name);
  14328. } while (!_dos_findnext(fib) &&
  14329. *subfifoptr <
  14330. &subfifo[(_MAX_FNAME - 1) * MAX_SUB]);
  14331. **subfifoptr = NULL; /* terminate FIFO */
  14332. *subfifoptr = subfifo; /* reset FIFO pointer */
  14333.  
  14334. /* End of File */
  14335.  
  14336.  
  14337. Listing 3
  14338. /*
  14339. LISTER: a subfunction to link to recursive main for
  14340.  
  14341. listing wildcard file matches.
  14342. */
  14343.  
  14344. #include <stdio.h>
  14345.  
  14346. void subfunc(char *path)
  14347. {
  14348. printf("\n%s", path);
  14349. }
  14350.  
  14351. /* End of File */
  14352.  
  14353.  
  14354. Listing 4
  14355. /*
  14356. REMOVE: a subfunc to link to recursive main to
  14357. do wildcard file deletes.
  14358. */
  14359.  
  14360. #include <stdio.h>
  14361.  
  14362. void subfunc(char *path)
  14363. {
  14364. remove(path);
  14365. }
  14366.  
  14367. /* End of File */
  14368.  
  14369.  
  14370. Listing 5
  14371. /*
  14372. A wildcard search main for the AMIGA workstation.
  14373. 2 switches are presently supported: /t for totals
  14374. and /s for subdirectories.
  14375. */
  14376.  
  14377. #include <functions.h> /* contains pragmas for system */
  14378. #include <arpbase.h> /* contains pragmas for ARP calls */
  14379.  
  14380. #define MAX_PATH 256
  14381.  
  14382. struct ArpBase *ArpBase;
  14383. struct AnchorPath anchor;
  14384. char pattern[128];
  14385. int total;
  14386.  
  14387. void subfunc(char *path);
  14388.  
  14389. void main(int argc, char **argv)
  14390. {
  14391. if (argc < 2)
  14392. {
  14393. printf("\n\t%s <filespec> [/s/t]\n", argv[O]);
  14394. printf("\tFilespec can have DOS wildcards!\n"
  14395. "\t/S switch includes subdirectories.\n"
  14396. "\t/T switch gives a total.\n");
  14397. exit(0);
  14398. }
  14399.  
  14400.  
  14401. ArpBase = OpenLibrary("arp.library", 39);
  14402. if (!ArpBase)
  14403. {
  14404. printf("ERROR: couldn't open ARP!!!\n");
  14405. exit(-1);
  14406. }
  14407.  
  14408. PreParse(BaseName(argv[1]), pattern);
  14409. strcpy(BaseName(argv[1]), "*");
  14410. anchor.ap_Flags = APF_DoWild;
  14411. anchor.ap_StrLen = MAX_PATH;
  14412.  
  14413. if (!FindFirst(argv[1], &anchor))
  14414. do {
  14415. if (anchor.ap_Info.fib_DirEntryType > 0)
  14416. {
  14417. if (!(anchor.ap_Flags & APF_DidDir) &&
  14418. argc > 2 && strstr(argv[2], "/S"))
  14419. anchor.ap_Flags = APF_DoDir;
  14420. anchor.ap_Flags &= ~APF_DidDir;
  14421. }
  14422. else
  14423. if (PatternMatch(pattern,
  14424.  anchor.ap_Info.fib_FileName))
  14425. {
  14426. total++;
  14427. subfunc(anchor.ap_Buf);
  14428. }
  14429. } while(!FindNext(&anchor));
  14430.  
  14431. if (argc > 2 && strstr(argv[2], "/T"))
  14432. printf("\tTOTAL = %d\n\n", total);
  14433.  
  14434. CloseLibrary(ArpBase);
  14435. }
  14436. /* End of File */
  14437.  
  14438.  
  14439. Listing 6
  14440. /* RECUR is a recursive descent subroutine to search
  14441. directories and subs with wildcard and path
  14442. capabilities -> then pass the path name
  14443. to a user supplied function called "subfunc".
  14444. 2 switches are presently supported: totals
  14445. and subs for subdirectories.
  14446. */
  14447. #include <dos.h>
  14448. #include <stdio.h>
  14449. #include <stdlib.h>
  14450. #include <malloc.h>
  14451. #include <string.h>
  14452.  
  14453. #define MAX_SUB 128
  14454.  
  14455. extern void subfunc(char *path);
  14456.  
  14457. int recurs(char *path, int subs, int totals)
  14458. {
  14459. struct find_t *fib;
  14460.  
  14461. char *drive, *dir, *fname, *ext;
  14462. char *tmppath, *tmpdir;
  14463. char (*subfifoptr)[_MAX_FNAME];
  14464. char *subfifo;
  14465. int total = 0;
  14466. /* malloc so we don't blow up the stack */
  14467. fib = malloc(sizeof(struct find_t));
  14468. drive = malloc(_MAX_DRIVE);
  14469. dir =malloc(_MAX_DIR);
  14470. fname = malloc(_MAX_FNAME);
  14471. ext = malloc(_MAX_EXT);
  14472. tmpdir = malloc(_MAX_DIR);
  14473. tmppath = malloc(_MAX_PATH);
  14474.  
  14475. if (subs)
  14476. {
  14477. /* if subs switch - do subdirectories first, RECURSIVELY */
  14478. subfifo = *subfifoptr =
  14479. (char *)malloc(_MAX_FNAME * MAX_SUB);
  14480. _splitpath(path, drive, dir, fname, ext);
  14481. _makepath(tmppath, drive, dir, "*", "*");
  14482. If (!_dos_findfirst(tmppath, _A_SUBDIR, fib))
  14483. do
  14484. {
  14485. if (fib->name[0] != '.' && fib->attrib &
  14486.  _A_SUBDIR)
  14487. strcpy(*subfifoptr++, fib->name);
  14488. } while (!_dos_findnext(fib) && *subfifoptr <
  14489. &subfifo[(_MAX_FNAME - 1) * MAX_SUB]);
  14490. **subfifoptr = NULL; /* terminate fifo */
  14491. *subfifoptr = subfifo; /* reset fifo pointer */
  14492. while(**subfifoptr) /* while not at the end */
  14493. {
  14494. strcpy(tmpdir, dir);
  14495. _makepath(tmppath, drive,
  14496. strcat(tmpdir, *subfifoptr++), fname, ext);
  14497. total += recurs(tmppath, subs, totals);
  14498. }
  14499. free(subfifo);
  14500. }
  14501. if (!_dos_findfirst(path, _A_NORMAL, fib))
  14502. do
  14503. {
  14504. _splitpath(path, drive, dir, fname, ext);
  14505. strcpy(tmppath, drive);
  14506. strcat(tmppath, dir);
  14507. subfunc(strcat(tmppath, fib->name));
  14508. total++;
  14509. } while (!_dos_findnext(fib));
  14510. if (totals)
  14511. printf("\ntotal = %d\t%s files\n", total, path);
  14512.  
  14513. free(fib), free(drive), free(dir), free(fname);
  14514. free(ext), free(tmpdir), free(tmppath);
  14515. return total;
  14516. }
  14517. End of File */
  14518.  
  14519.  
  14520.  
  14521.  
  14522.  
  14523.  
  14524.  
  14525.  
  14526.  
  14527.  
  14528.  
  14529.  
  14530.  
  14531.  
  14532.  
  14533.  
  14534.  
  14535.  
  14536.  
  14537.  
  14538.  
  14539.  
  14540.  
  14541.  
  14542.  
  14543.  
  14544.  
  14545.  
  14546.  
  14547.  
  14548.  
  14549.  
  14550.  
  14551.  
  14552.  
  14553.  
  14554.  
  14555.  
  14556.  
  14557.  
  14558.  
  14559.  
  14560.  
  14561.  
  14562.  
  14563.  
  14564.  
  14565.  
  14566.  
  14567.  
  14568.  
  14569.  
  14570.  
  14571.  
  14572.  
  14573.  
  14574.  
  14575.  
  14576.  
  14577.  
  14578.  
  14579.  
  14580.  
  14581.  
  14582.  
  14583.  
  14584. Curve Fitting By Chebyshef And Other Methods
  14585.  
  14586.  
  14587. Timothy Prince
  14588.  
  14589.  
  14590. Tim Prince has a B.A. in physics from Harvard and a Ph.D. in mechanical
  14591. engineering from the University of Cincinnati. He has 25 years of experience
  14592. in aerodynamic design and computation. He can be contacted at 39 Harbor Hill
  14593. Dr., Grosse Pointe Farms, MI 48236.
  14594.  
  14595.  
  14596. The problem of fitting a curve to data as a convenient means for interpolating
  14597. values has almost as many different solutions as there are people trying to
  14598. find solutions. While there are accepted methods (such as Fourier transforms)
  14599. for certain specialized applications, the field is wide open for many common
  14600. applications.
  14601. Some of the most common methods fall in the regression category, where you
  14602. pick a form of curve, perhaps a polynomial of degree N, and fit it to the data
  14603. by least squares. Typically, you may have little idea whether the family of
  14604. curves you selected is appropriate for the application. Choosing too high an
  14605. order of polynomial makes the shape of the curve too sensitive to
  14606. uncertainties in the data points. To avoid the problem you can fit
  14607. successively higher order polynomials, using statistical tests to decide where
  14608. to stop. Least squares methods cannot fit a curve with much more than half the
  14609. precision of the underlying computer arithmetic operations.
  14610. Vendors of CAD systems have found their own ways to fit curves and surfaces to
  14611. geometrical data. Their problem is complicated by the need to deal three
  14612. dimensions, but the methods are generally based on two dimensional curves. The
  14613. B(ezier) splines are popular in this field, but require a certain art in
  14614. finding ways to develop and apply the software. No doubt the experts see the
  14615. artistic content as an advantage. Obviously, satisfactory surfaces can be
  14616. drawn by interactive graphic methods. On the other hand, blindly shoving data
  14617. in is likely to give poor results.
  14618.  
  14619.  
  14620. Cubic Splines
  14621.  
  14622.  
  14623. Typical engineering software uses linear or spline curve interpolation between
  14624. data points. The standard spline methods fit a curve with continuous slope and
  14625. curvature passing exactly through each data point (referred to as knots).
  14626. Appropriate conditions must be chosen for the first and final data points, and
  14627. this is often difficult to do. General purpose cubic spline functions, such as
  14628. the published de Boor method, offer a choice of three types of end condition:
  14629. specified slope
  14630. specified curvature
  14631. "not-a-knot" smooth ends
  14632. The specified curvature condition is often misused. Zero curvature at the end
  14633. is called the "natural" condition, with reference to the physical spline
  14634. analogy, but that doesn't mean you should use it. If there is no way to know
  14635. what the curvature should be, the "not-a-knot" end condition is usually
  14636. better. This adjusts the end curvature and slope to give the smoothest
  14637. possible curve. In general, the third derivative of the spline (analogous to
  14638. shear stress in a beam) changes abruptly at each knot. With the "not-a-knot"
  14639. end condition, this discontinuity is eliminated at the knot next to the end,
  14640. analogous physically to putting torque on the end of the spline so that it
  14641. does not need restraint to pass through the knot next to the end.
  14642.  
  14643.  
  14644. Spline Software Issues
  14645.  
  14646.  
  14647. Even with intelligent use of splines, problems remain. When all the end
  14648. condition options are included, it may be difficult to figure out how to
  14649. structure code. Much published software is less efficient than it could be.
  14650. Consider the spline evaluation code from Numerical Recipes In C (Press,
  14651. Flannery, Teukolsky, and Vetterling
  14652. a= (xa[khi]-x)/(h=xa[khi]-xa[klo]);
  14653. b= (x-xa[klo] )/h;
  14654. *y= a*ya[klo]+b*ya[khi]+((a*a*a-a)*y2a[klo]
  14655. + b*b*b-b)*y2a[khi])*h*h/6;
  14656. and the algebraic equivalent
  14657. b= x-xa[klo];
  14658. a= xa[khi]-x;
  14659. h= xa[khi]-xa[klo];
  14660. *y= ((ya[klo]-y2a[klo]/6*b*(a+h))*a+(ya[khi]
  14661. -y2a[khi]/6*a*(b+h))*b)/h;
  14662. The second version saves four operations and the corresponding roundoff
  14663. errors.
  14664. The spline fit function in Numerical Recipes In C has a problem with a
  14665. potential aliasing of array elements which can force the compiler to generate
  14666. less efficient code. The problem can be alleviated simply by changing the
  14667. order of lines in the code. The prototype reads
  14668. void spline(float x[]
  14669. float y[],int n,float y2[])
  14670. Only the array y2[] is to be changed by spline(). But the C compiler does not
  14671. know that the array arguments do not overlap, so the compiler must assume that
  14672. any change to y2[] could also affect x[] or y[], preventing it from reusing
  14673. intermediate values. Therefore, the assignment to y2[i] should occur last in
  14674. the loop body. This change allows the compiler to perform normal
  14675. optimizations, short of taking advantage of loop unrolling. Such aliasing is
  14676. an issue for any C compiler trying to combine common subexpressions.
  14677. Intermediate results can be saved where they will be used again on the next
  14678. step. Since the algorithm is recursive anyway, the additional recurrences will
  14679. not pose an obstacle to any known compilers, as they might in parallelizable
  14680. code. The improvement in speed is not as large as might be expected;
  14681. sequential processors do not have enough registers and must use memory storage
  14682. of these temporaries, and pipeline processors can handle many of the duplicate
  14683. floating point operations by increasing parallelism.
  14684.  
  14685.  
  14686. Use Of Splines
  14687.  
  14688.  
  14689. You may wish to use the fitted curve to find the slope of the data. Averaging
  14690. the slope of the cubic spline curve and the slope of the linear interpolant
  14691. gives a good estimate at points half way between knots, but this slope does
  14692. not match the slope of any smooth curve through the data. As end slope is
  14693. determined by your choice of end condition, slopes closer to the end points
  14694. are as unreliable as extrapolating the curve beyond the end points.
  14695. Integration of the spline fit to find the area under a curve gives slightly
  14696. over half the error of Simpson's rule integration using the same data points.
  14697. This improvement is hardly worth the extra effort unless it is impossible to
  14698. get the data spaced appropriately for Simpson's rule.
  14699. A cubic spline curve exaggerates errors or uncertainty in the data points.
  14700. Usual ways of dealing with this are to use the "spline in tension" method, or
  14701. to put "springs" between the curve and the knots and allow the curve to adjust
  14702. to greater smoothness by deviating slightly from the input data. Tension or
  14703. stiffness of the springs is an arbitrary parameter, requiring human
  14704. interaction to get a visually satisfactory result. Repeated smoothing keeps on
  14705. changing the data; unlike the Chebyshef smoothing, the adjusted splines never
  14706. produce a curve which doesn't want to be adjusted further.
  14707. In practice, there is a fairly low limit (around 15) to the number of points
  14708. which can be spline fit reliably without provision for tension or data
  14709. adjustment. Demanding geometric problems, like fitting the contour of an
  14710. airplane wing, can be handled by fitting splines in several segments.
  14711. A tactic which may be useful for geometric curve fitting by splines or any
  14712. other method is to parameterize x, y, and z as a function of stair step
  14713. distance. Ideally, the arc length along the curve would be a parameter; the
  14714. stairstep distance is much easier to obtain. Fitting two or three functions of
  14715. one independent variable is not much more time-consuming.
  14716.  
  14717.  
  14718. Chebyshef Polynomials
  14719.  
  14720.  
  14721.  
  14722. Chebyshef polynomials provide a well founded way of fitting a curve which
  14723. approximates a function within a finite interval. The method seems almost too
  14724. magical to trust, requiring less arbitrary input and often giving more
  14725. satisfactory results than alternative methods. Dropping a term from a
  14726. Chebyshef fitted polynomial introduces errors which are nowhere larger than
  14727. the associated coefficient, so you can easily see which terms to keep to reach
  14728. the desired accuracy of curve fit. When additional terms don't yield further
  14729. convergence, it is good evidence that the background noise in the data or the
  14730. limited accuracy of the arithmetic is swamping the remaining terms.
  14731. The theory is covered well in the textbooks, and a reliable set of C functions
  14732. are presented in Numerical Recipes In C. I will concentrate on ways to use
  14733. Chebyshef fits, and some implementation issues which should be addressed for
  14734. best results.
  14735. Determination of Chebyshef coefficients requires the function values to be
  14736. supplied at points dictated by the method, where the polynomial will match the
  14737. specified values. Using specific points may seem awkward when the control
  14738. points consist of arbitrary data at arbitrary locations, but Chebyshef fitting
  14739. has been applied successfully by using linear interpolated values. Placement
  14740. of the input points is less critical than for splines, although, since the
  14741. function evaluations are concentrated toward the ends of the interval, the
  14742. data are weighted more heavily there. The number of function evaluations
  14743. needed for Chebyshef fitting is the minimum required to specify a given number
  14744. of polynomial terms, so this method is as economical as any for functions
  14745. which are expensive to evaluate. A dozen Chebyshef terms are likely to be
  14746. sufficient for close approximation to any smooth curve.
  14747. The authors of Numerical Recipes in C advise against converting the Chebyshef
  14748. polynomial into an ordinary polynomial before evaluation. This advice makes
  14749. sense when there will be few evaluations of the polynomial, or when little is
  14750. known about its behavior. Existing software often violates this advice,
  14751. possibly because Clenshaw's algorithm was not well known until recently.
  14752. Clenshaw's algorithm is 30 percent faster than evaluation by more obvious
  14753. methods on sequential processors; even with careful coding for best
  14754. performance, the difference drops to 20 percent on typical pipeline
  14755. processors, but the small advantage in accuracy remains.
  14756.  
  14757.  
  14758. Chebyshef Performance Tuning
  14759.  
  14760.  
  14761. The routine for calculating the Chebyshef coefficients consumes the most time
  14762. and generates most of the roundoff error when all arithmetic operations are
  14763. done at the same precision. cos() is evaluated with arguments up to n*PI for a
  14764. Chebyshef fit of order n. If the arguments have a relative error of order
  14765. DBL_EPSILON, the results may be in error by order n*DBL_EPSILON. Summing up
  14766. the results introduces smaller errors, which are still significant if accurate
  14767. results from cos() are obtained. If these roundoff errors can be controlled,
  14768. the overall roundoff errors in the Chebyshef fit and evaluation can be held to
  14769. about DBL_EPSILON. Unfortunately, most compilers fail to take advantage of the
  14770. ability of 8087 or 68881 processors to hold the errors generated in the inner
  14771. loop to the order of n*DBL_EPSILON. Even without extra precision, the accuracy
  14772. of Chebyshef fitting is far better than least squares methods.
  14773. On co-processor architectures which have a built-in cosine function, you
  14774. should avoid going through subroutine calls that narrow arguments and results
  14775. to double precision. The greatest accuracy improvement comes from use of a
  14776. long double value for PI. If the system does not support true long double
  14777. constants, you may be able to generate a long double value which can be
  14778. obtained by tricks such as
  14779. #define PI
  14780. (sqrt(3.125*3.125)+.01659265358979323846)
  14781. Putting a function call in the expression prevents most compilers from
  14782. performing the arithmetic and narrowing the result. Using a long double value
  14783. for PI reduces the roundoff errors, typically by a factor of two compared to
  14784. errors generated by using a normal double value for PI. Roundoff error may be
  14785. reduced by an additional factor of three by taking the assembly code output
  14786. from the C compiler and changing the cos() calls to direct numeric processor
  14787. instructions. Sun's inline function compilation option doesn't do the job,
  14788. since it still requires "arguments" and "returns" narrowed to double and
  14789. pushed on the stack. With full use of register variables, the 8087 and 68881
  14790. families are excellent for Chebyshef analysis.
  14791. Pipelined processors should have a pipelined version of cos(), since it should
  14792. be possible to calculate a group of four cosines as fast as two individual
  14793. cosines. An intermediate level of performance may be obtained by supplying
  14794. source code for cos() and using an inlining compiler option. While cosf() can
  14795. be handled by a macro, the greater number of terms in cos() makes such an
  14796. approach impractical. The subject of group math library functions could fill
  14797. another article.
  14798. The recursive algorithms for evaluation of Chebyshef polynomials can be
  14799. speeded up on superscalar architectures by determining which array element is
  14800. needed first, and writing the code to fetch that element prior to the loop
  14801. iteration in which it is needed. This is particularly true of the Chebyshef
  14802. derivative coefficient evaluation. As presented in Numerical Recipes In C,
  14803. this function again suffers from aliasing, since the compiler cannot be sure
  14804. that the read-only input array c[] will not be overwritten. In order to keep
  14805. the pipeline running, the potentially aliased array element has be fetched
  14806. before the results of the current iteration are stored. In addition, all but
  14807. two iterations of the second loop can be moved into the first loop where they
  14808. will fill otherwise empty pipeline slots. There are enough registers even on
  14809. an 8087 to eliminate all redundant memory references, further reducing the
  14810. aliasing problem. Listing 1 shows the details.
  14811. These changes are detrimental to readability but can triple the performance
  14812. and reduce the size of the generated code. Overlapping of stages of a
  14813. sequential calculation from different loop iterations is reminiscent of CDC
  14814. 6600 style code. In accordance with the old saw, a FORTRAN programmer can
  14815. write FORTRAN in any language.
  14816. The aliasing problem could be eliminated by storing results in a temporary
  14817. array, then copying this array to the destination. As long as a loop does not
  14818. involve reading and writing different external arrays, with one array accessed
  14819. via a pointer, there is no aliasing problem. The temporary vector solution is
  14820. common for vectorizable code, but in simple problems involving recurrence,
  14821. better results can be obtained with pump priming methods as shown previously.
  14822.  
  14823.  
  14824. Chebyshef Economized Polynomials
  14825.  
  14826.  
  14827. A common end use of Chebyshef polynomials is in computational approximations
  14828. for math library functions. In this application, the goal is to fit a
  14829. polynomial with the minimum number of terms needed to give the required
  14830. precision. Typically, the base interval is chosen to be symmetric about zero,
  14831. so that there are only odd power terms. The interval will be one in which the
  14832. Taylor series converges well, and a Chebyshef economized polynomial converges
  14833. even better. The caution about possible inaccuracy of polynomial evaluation
  14834. does not apply.
  14835. In order to get a series of sufficient accuracy to use in a library function,
  14836. the whole Chebyshef fitting process has to be carried out in a higher
  14837. precision. long double precision should be sufficient to calculate
  14838. coefficients for double precision function approximations.
  14839. If a Chebyshef series is fitted directly to a function which has zeros, there
  14840. will be errors near the zeros which are large relative to the local value of
  14841. the function, even though they are less than the errors at the end of the
  14842. interval. One way of getting around this is to subtract off the linear term of
  14843. a series expansion around the zero, and make the Chebyshef fit to the
  14844. remainder. Similar tactics might be applied where the function is known to be
  14845. approximated by a more complicated function.
  14846. A better method for the standard library functions is to divide out the zero,
  14847. so that the Chebyshef polynomial is fit to a function such as sin(x)/x. The
  14848. Chebyshef polynomial will have only even terms, with the size of the odd terms
  14849. calculated being an indication of numerical error. The end result will be an
  14850. approximation which has less relative error over most of the interval than a
  14851. minimax polynomial obtained by laborious iteration to minimize the maximum
  14852. relative errors. The general strategy is to approximate a function by a known
  14853. dominant term times a Chebyshef polynomial which takes care of the fine
  14854. adjustments.
  14855. Most Chebyshef economized polynomials can be determined by straightforward
  14856. programming using a system with long double arithmetic or a multiple precision
  14857. package. In some cases, as with log((1+x)/(1-x)), it is better to derive a
  14858. Taylor series which matches the form of the planned approximation and fit the
  14859. Chebyshef polynomial to this series. Working out the cancelling terms
  14860. algebraically rather than numerically avoids much of the roundoff error.
  14861. Normally, Chebyshef economized polynomials would be used only as a software
  14862. development tool, to provide a best fit, most economical polynomial for quick
  14863. evaluation of a function. If the number of evaluations of a curve is not
  14864. great, or the convergence properties of a polynomial are unknown, it is better
  14865. to use the Clenshaw algorithms for evaluation. Still, an economized polynomial
  14866. might well be better than a least squares fit regression, and one might want
  14867. to find such a polynomial for comparison.
  14868.  
  14869.  
  14870. Rational Polynomials
  14871.  
  14872.  
  14873. Rational polynomials form a better fit to some types of functions. A clue that
  14874. this may be the case is an inversion symmetry, as in
  14875. exp(x) = 1/exp(-x)
  14876. or
  14877. tan(PI/2-x) = 1/tan(x)
  14878. Both of these functions are best fit by rational polynomials. Unfortunately,
  14879. there is no accepted method for making such numerical approximations. You can
  14880. usually guess the form of the series; since
  14881. tan(x) = -tan(-x)
  14882. the rational polynomial approximation will have odd power terms in the
  14883. numerator and even powers in the denominator. An approximation for exp()
  14884. should be of the form
  14885. (p(x)+q(x))/(p(x)-q(x))
  14886. with p having only even power terms and q having only odd power terms, in
  14887. order to exhibit the required symmetry. This is equivalent to an approximation
  14888. for tanh(x) of the form
  14889. q(2x)/p(2x)
  14890. which is a way to evaluate tanh() for small x without losing accuracy.
  14891. You could arrive at similar results by deriving continued fraction
  14892. approximations and converting to rational polynomials, a process which might
  14893. be educational but wouldn't help in terms of practical results. The
  14894. coefficients obtained analytically for rational polynomials (for unbounded
  14895. intervals) are likely to be far from optimum for a closed interval.
  14896. People who play with these approximations have their favorite iterative
  14897. numerical methods for adjusting a reasonable guess at the coefficients to
  14898. greater accuracy. These calculations take almost twice as many bits of
  14899. precision as the desired final accuracy; you would be lucky to get an
  14900. approximation accurate to 12 decimal places on an 8087. Even IBM 3081 quad
  14901. precision is marginal for obtaining a rational approximation for tan(). The
  14902. author has had satisfactory results from a method acquired from Ruzinsky,
  14903. which runs 10 to 20 seconds on a VAX 8550. With a multiple precision software
  14904. package, higher accuracy fits should be feasible but time-consuming.
  14905.  
  14906.  
  14907. Conclusion
  14908.  
  14909.  
  14910. Several popular methods for curve fitting have applications for which they are
  14911. well suited; some have been used where they work less well. The simplicity of
  14912. the popular spline methods is gained at a cost in reliability. Spline methods
  14913. work well when the end conditions can be specified accurately, and the data
  14914. points are accurate. Rational polynomials are best, in the author's
  14915. experience, only when they model a known symmetry. The Chebyshef methods are
  14916. effective and deserve wider use. Chebyshef methods can be used for smoothing
  14917. and interpolating discrete data which are moderately uncertain, as well as for
  14918. evaluation of fits to known functions. Recursive algorithms for accurate
  14919. evaluation of Chebyshef polynomials can be organized for superscalar
  14920. performance on pipelined architectures.
  14921. References
  14922. Deboor, C. A Practical Guide to Splines, Springer-Verlag, 1978.
  14923. Press, W.H., Flannery, B.P., Teukolsky, S.A., Vetterling, W.T., Numerical
  14924. Recipes In C, Cambridge University Press, 1988.
  14925. Ruzinsky, Steven A., "A Simple Minimax Algorithm," Dr. Dobb's Journal, #93,
  14926. pp. 84-91, July 1984.
  14927.  
  14928. Fundamentals Of Curve Fitting
  14929. Steve Graham
  14930. Curve fitting is something of a misnomer; a better name might be "function
  14931. finding" -- the goal is to find a function that satisfies certain constraints.
  14932. The constraints normally take one of two forms: a list of control points
  14933. (samples from some process to be modelled), or a specific function (too
  14934. difficult or costly to evaluate). These constraints mirror the two goals of
  14935. curve fitting: interpolation and approximation.
  14936. Interpolation is the practice of using function values at some points (usually
  14937. samples, with the actual function unknown) to estimate the value of the
  14938. function at other points. If the function argument to be evaluated lies
  14939. outside the range of the sampled points, the process is referred to as
  14940. extrapolation. The simplest example is linear interpolation: find the two
  14941. control points nearest the argument, assume the function corresponds to a
  14942. straight line between the two points, and calculate the estimated function
  14943. value. This may be familiar from interpolating log tables. Graphically, linear
  14944. interpolation produces a "connect-the-dots" image from the control points.
  14945. Because of the cost of evaluating some functions, it is preferable to use a
  14946. fast approximating function, as long as the approximation is sufficiently
  14947. accurate. Since the original function is known, the implementor has more
  14948. control over the number and placement of control points, as well as a standard
  14949. to measure the accuracy of the fit against.
  14950. When fitting a curve to control points, there are two preliminary choices. The
  14951. curve can be created from all the control points, or it can be created in
  14952. segments, short curves between adjacent control points. The curve can be
  14953. required to match the control points exactly or the curve may be allowed to
  14954. diverge, creating local errors in the interest of minimizing global error.
  14955. For N control points, a unique polynomial of degree N-1 can match the points
  14956. precisely. Lagrange's interpolation formula provides an expression for the
  14957. polynomial
  14958. Equation 0
  14959. Suppose we want to find a quadratic polynomial through: (1,0), (2,1), (3,4).
  14960. Lagrange's formula yields
  14961. Click Here for Equation
  14962. which simplifies to
  14963. Click Here for Equation
  14964. Spline techniques treat segments individually, producing a composite curve
  14965. from the segments. A spline was a flexible ruler used in drafting. The spline
  14966. was anchored to the control points (referred to as "knots"), allowing a smooth
  14967. curve to be drawn. The functions fitting individual segments are generally
  14968. simple. A popular choice is a cubic polynomial (hence, "cubic splines"), of
  14969. the form
  14970. P (x) = ax3 + bx2 + cx + d
  14971. Cubic splines produce a relatively smooth curve, since adjacent segments are
  14972. normally forced to have identical slope (first derivative) and curvature
  14973. (second derivative) at the knots. Constraints may be stated by requiring
  14974. continuity of the first and second derivatives of the composite function at
  14975. the knots. The constraints and the knot coordinates, can be used to determine
  14976. the coefficients for the fitting cubic polynomial. The first and last knots
  14977. cause a problem, since there is no adjacent segment to constrain the values
  14978. for the slope and curvature; other criteria must determine the coefficients
  14979. for the end segments, such as the "natural" spline that sets the curvature at
  14980. the first and last knots to zero.
  14981. Two popular measures of the quality of a curve fit are the least squares
  14982. method and the minimax method. The least squares techniques seek to minimize
  14983. the sum of the squares of the divergence between the approximating function
  14984. and the control points or original function. Minimax techniques use the
  14985. alternative aim of minimizing the maximum error between the new function and
  14986. the source data or function.
  14987. Chebyshef (also, Chebyshev, Chebichev or Tchebycheff) fitting relies on a
  14988. combination of Chebyshef polynomials to approximate the function. A Chebyshef
  14989. polynomial has the form
  14990. Tn(x) = cos (n arccos x)
  14991. Note the recurrence relation between subsequent Chebyshef polynomials
  14992. To (x) = 1
  14993. T1 (x) = x
  14994. T2 (x) = 2x2 - 1
  14995. T3 (x) = 4x3 - 3x
  14996. Tn+1 (x) = 2xTn (x) - Tn-1 (x) n >= 1
  14997. Clenshaw's recurrence formula provides an efficient way fo evaluating such
  14998. equations.
  14999.  
  15000.  
  15001. Aliasing And Optimization
  15002.  
  15003.  
  15004. When a programming language allows two apparently different access mechanisms
  15005. to address the same data elements, this is referred to as aliasing. Having
  15006. multiple names for the same objects is considered bad style and discouraged,
  15007. since it adds to the complexity of understanding a program. The possibility of
  15008. aliasing is almost impossible to eliminate from programming languages:
  15009. subroutine calls with one data object used for two arguments and mapped to two
  15010. different formal parameters can cause aliasing. The most common cause for
  15011. aliasing problems, is access to data through a pointer, where two different
  15012. references could access the same data.
  15013. Besides interfering with comprehension, aliasing becomes an optimization
  15014. problem for subroutines. The simplest and most common optimization performed
  15015. by compilers is the reuse of values. If X has already been loaded into a
  15016. register for use by the previous statement, it needn't be loaded again. If an
  15017. intermediate value (a subexpression), say B*B - 4*A*C, has been calculated
  15018. once, it needn't be calculated again. Two problems can interfere with reusing
  15019. values: unsufficient registers to hold the values and possible changes to
  15020. constituent variables. The available registers are a simple consequence of the
  15021. CPU's architecture and the compiler's conventions for register use. Possible
  15022. changes arise because of aliasing and side effects. The compiler must
  15023. guarantee correctness, so if aliasing is possible and a write occurs, saved
  15024. values must be flushed, since they are no longer reliably accurate. Similarly,
  15025. if a function call occurs that could cause side effects -- even if the
  15026. function call mechanism preserves register contents -- the saved values are no
  15027. longer necessarily correct.
  15028. In practice possible aliasing and function calls should be a concern in tight,
  15029. inner loops that are executed repeatedly. Inline code and careful statement
  15030. ordering can permit compilers to perform optimizations that otherwise might
  15031. introduce errors.
  15032. The term "aliasing" is also used in graphics and sampling to refer to
  15033. artifacts created by the granularity of the sample or representation; such
  15034. aliasing may create jagged edges in images.
  15035.  
  15036. Listing 1
  15037. void chder(a,b,c,cder,n)
  15038. double a,b,c[],cder[]; /* c[] and cder[] must not overlap */
  15039. int n;
  15040. {
  15041. register int j;
  15042. register double con,cj1,cdj2,cdj1,cdj;
  15043. con = 2/(b-a);
  15044. cdj1 = 2*(n-1)*c[n-1];
  15045. cj1 = 2*(n-2)*c[n-2];
  15046. cder[n-1] = cdj2 = 0;
  15047. for(j = n-2; -j >= 0;){
  15048. cdj = cdj2+cj1; /* the recursive result of first loop */
  15049. cj1 = 2*j*c[j]; /* prefetch for next loop iteration */
  15050. cder[j+1] = con*(cdj2=cdj1); /* complete final loop */
  15051. cdj1 = cdj; /* compiler doesn't worry about aliasing of locals */
  15052. }
  15053. cder[0] = con*cdj1;
  15054. }
  15055.  
  15056. /* End of File */
  15057.  
  15058.  
  15059.  
  15060.  
  15061.  
  15062.  
  15063.  
  15064.  
  15065.  
  15066.  
  15067.  
  15068.  
  15069.  
  15070.  
  15071.  
  15072.  
  15073.  
  15074.  
  15075.  
  15076.  
  15077.  
  15078.  
  15079.  
  15080.  
  15081.  
  15082.  
  15083.  
  15084.  
  15085.  
  15086.  
  15087.  
  15088.  
  15089.  
  15090.  
  15091.  
  15092.  
  15093.  
  15094.  
  15095.  
  15096.  
  15097.  
  15098.  
  15099.  
  15100.  
  15101.  
  15102.  
  15103.  
  15104.  
  15105.  
  15106.  
  15107.  
  15108.  
  15109.  
  15110.  
  15111.  
  15112.  
  15113.  
  15114.  
  15115.  
  15116.  
  15117.  
  15118.  
  15119.  
  15120.  
  15121.  
  15122.  
  15123.  
  15124. Standard C
  15125.  
  15126.  
  15127. Primitives For <stdio.h>
  15128.  
  15129.  
  15130.  
  15131.  
  15132. P.J. Plauger
  15133.  
  15134.  
  15135. P.J. Plauger is senior editor of The C Users Journal. He is secretary of the
  15136. ANSI C standardMs committee, X3J11, and convenor of the ISO C standards
  15137. committee, WG14. His latest book is The Standard C Library, published by
  15138. Prentice-Hall. You can reach him at PJP@wsa.oz; or uunet!munnari!wsa.oz!pjp.
  15139.  
  15140.  
  15141.  
  15142.  
  15143. Introduction
  15144.  
  15145.  
  15146. Last month, I began discussing how to implement the functions declared in the
  15147. header <stdio.h>. I showed my version of that header file and described the
  15148. FILE data structure. I also walked through the code needed to open and close
  15149. files. (See "Implementing <stdio.h>," Standard C, CUJ January, 1992.)
  15150. This month, I present the low-level "primitive" functions in <stdio.h> that
  15151. must be tailored to each operating system. A primitive is a function that
  15152. performs some essential operation. No other combination of library functions
  15153. can substitute. You can keep most of the library portable if you isolate
  15154. system dependencies in a minimum set of primitives.
  15155. As I mentioned last month, this implementation has nine such functions Each
  15156. primitive function must perform a standardized operation for the Standard C
  15157. library. Each must also be reasonably easy to tailor for the divergent needs
  15158. of different operating systems.
  15159.  
  15160.  
  15161. The Primitives
  15162.  
  15163.  
  15164. Three of the primitives are also standard functions:
  15165.  remove -- Remove a named file.
  15166.  rename -- Change the name of a file.
  15167.  tmpnam -- Construct a reasonable name for a temporary file.
  15168. Each of these functions is small and very dependent on the peculiarities of
  15169. the underlying operating system. It is not worth writing any of them in terms
  15170. of lower-level primitives. You can often find versions in an existing C
  15171. library that do the job nicely.
  15172. Three of the primitives are macros defined in the internal header "yfuns.h".
  15173. It defines macros and declares functions needed only within the Standard C
  15174. library to interface to the outside world. Only certain functions written for
  15175. this implementation need include "yfuns.h". (The internal header <yvals.h>, by
  15176. contrast, must be included in several standard headers.)
  15177. The three macros look like internal functions with the declarations
  15178. int _Fclose(FILE *str);
  15179. int _Fread(FILeE *str, char *buf, int size);
  15180. int _Fwrite(FILE *str, const char *buf, int size);
  15181. Their semantics are:
  15182.  _Fclose -- Close the file associated with str. Return zero if successful.
  15183.  _Fread -- Read up to size characters into the buffer starting at buf from the
  15184. file associated with str. Return the number successfully read, or zero if at
  15185. end-of-file, or a negative error code if a read error occurs.
  15186.  _Fwrite -- Write size characters from the buffer starting at buf to the file
  15187. associated with str. Return the number of characters actually written or a
  15188. negative error code if a write error occurs.
  15189. Many operating systems support functions that have declarations very similar
  15190. to these. You can often find existing functions that the macro expansions can
  15191. call directly.
  15192. The last three primitives are internal functions. One function is declared in
  15193. "xstdio.h". Two are used in masking macros, and hence are declared in
  15194. <stdio.h>. (See last month's presentation for a listing of this standard
  15195. header file.) Their declarations are:
  15196. short _Fopen(const char *name, unsigned short mode,
  15197.  const char *mods);
  15198. long _Fgpos(FILE *str, fpos_t *fpos);
  15199. int _Fspos(FILE *str, const fpos_t *fpos,
  15200.  long offset, int way);
  15201. Their semantics are:
  15202.  _Fopen -- Open the file with name name and mode mode (possibly using the
  15203. string mods as well). Return a non-negative handle if successful.
  15204.  _Fgpos -- If fpos is not a null pointer, store the file-position indicator at
  15205. fpos and return zero. Otherwise, encode the file-position indicator as a long
  15206. and return its value. Return the value EOF if not successful.
  15207.  _Fspos -- If way has the value SEEK_SET, set the file-position indicator from
  15208. either fpos or offset. (If fpos is not a null pointer, use the value stored in
  15209. fpos. Otherwise, decode offset to determine the file-position indicator.) If
  15210. way has the value SEEK_CUR, add offset to the file-position indicator.
  15211. Otherwise, way must have the value SEEK_END. Set the file-position indicator
  15212. to just beyond the last character in the file, plus offset. If successful,
  15213. return zero and clear _MEOF, _MREAD, and _MWRITE. Otherwise, return the value
  15214. EOF.
  15215. You are less likely to find existing functions that you can commandeer to
  15216. implement part or all of these three functions. Each involves data
  15217. representations that are probably peculiar to this implementation.
  15218.  
  15219.  
  15220. File Positioning Functions
  15221.  
  15222.  
  15223.  
  15224. Old C hands are probably comfortable with most of these primitives. They bear
  15225. a strong resemblance to a fistful of functions not included in the C Standard.
  15226. These have names such as close, open, read, and write. They are based on
  15227. system calls in the UNIX operating system. Early ports of C to other operating
  15228. systems retained this heritage as much as possible. The old primitives were
  15229. omitted from the C Standard for good reasons. Nevertheless, many of them still
  15230. provide a good foundation for the rest of the library.
  15231. The primitives you are likely to find strangest are _Fgpos and _Fspos. They
  15232. bear only a loose resemblance to their ancestor, called lseek. Most of the
  15233. difference is designed to accommodate two new file-positioning functions,
  15234. fgetpos and fsetpos. These have no exact analog in UNIX.
  15235. To show you why these new primitives take the form they do, I simply show how
  15236. the library uses them. The function fseek, for example, is simply
  15237. int fseek(FILE *str, fpos_t *p)
  15238. {
  15239. return (_Fspos(str, NULL,
  15240. off, smode));
  15241. }
  15242. Its brother ftell is
  15243. long ftell(FILE *str)
  15244. {
  15245. return (_Fgpos(str, NULL));
  15246. }
  15247. And rewind is
  15248. void rewind(FILE *str)
  15249. {
  15250. _Fspos(str, NULL, OL, SEEK_SET);
  15251. str->_Mode &= ~_MERR;
  15252. }
  15253. Similarly, the new function fgetpos is simply
  15254. int fgetpos(FILE *str, fpos_t *p)
  15255. {
  15256. return (_Fgpos(str, p));
  15257. }
  15258. And fsetpos is
  15259. int fsetpos(FILE *str,
  15260. const fpos_t *p)
  15261. {
  15262. return (_Fspos(str, p,
  15263. OL, SEEK_SET));
  15264. }
  15265. The results speak for themselves. These two primitives make the five
  15266. file-positioning functions trivial.
  15267.  
  15268.  
  15269. UNIX Primitives
  15270.  
  15271.  
  15272. By implementing these interface primitives, you can use this library in
  15273. conjunction with any reasonable operating systems. As I mentioned last month,
  15274. I have written sets of primitives for several popular operating systems. For
  15275. completeness, I show here primitives for just one environment. Please
  15276. remember, however, that these represent but one of many possibilities.
  15277. For simplicity, I sketch here primitives that interface to many versions of
  15278. the UNIX operating system. That is often the easiest system to use as a host
  15279. for the Standard C library. The C language has moved to many other
  15280. environments. Still, as I indicated above, much of the library design was
  15281. shaped by the needs and capabilities of UNIX. The files I show are only
  15282. sketches because they often can be augmented to advantage.
  15283. In all cases, I assume the existence of C-callable functions that perform UNIX
  15284. system calls without violating the name-space restrictions of Standard C. I
  15285. take the conventional UNIX name, make the first letter uppercase and prepend
  15286. an underscore. Thus, unlink becomes _Unlink. You may have to write these
  15287. functions in assembly language if your UNIX system supplies no adequate
  15288. substitutes.
  15289. For example, Listing 1 shows the file remove.c that defines the function
  15290. remove. This version simply invokes the UNIX system call _Unlink. A more
  15291. careful version would verify that a program with super-user permissions is not
  15292. doing something rash.
  15293. Listing 2 shows the file rename, c. It defines a simple version of rename that
  15294. simply manipulates links to the file. That typically works only if both the
  15295. new and old file names are within the same filesystem (on the same logical
  15296. disk partition). A more agressive version might choose to copy a file when the
  15297. link system service fails.
  15298. Listing 3 shows the file tmpnam.c. It defines a simple version of tmpnam that
  15299. concocts a temporary file name in the directory /tmp, the customary place for
  15300. parking temporary files. It encodes the current process-id to make a family of
  15301. names that should be unique to each thread of control.
  15302. Listing 4 shows the file xfopen.c that defines the function _Fopen. It maps
  15303. the codes I chose for the mode indicators to the codes used by the UNIX system
  15304. service that opens a file. A proper version of this program should not include
  15305. all these magic numbers. Rather, it should include the appropriate header that
  15306. UNIX provides to define the relevant parameters.
  15307. UNIX makes no distinction between binary and text files. Other operating
  15308. systems may have to worry about such distinctions at the time the program
  15309. opens a file. Similarly, UNIX has no use for any additional mode information.
  15310. (_Fopen could insist that the mode argument be an empty string here. This
  15311. version is not so particular.)
  15312. Listing 5 shows the file xfgpos.c that defines the function _Fgpos. It asks
  15313. the system to deliver the file-position indicator for the file, then corrects
  15314. for any data buffered on behalf of the stream. A file-position indicator under
  15315. UNIX can be represented in a long. Hence, type fpos_t, defined in <stdio.h>,
  15316. is a structure that contains only one long member. (I could have defined
  15317. fpos_t as type long directly, but I wanted to keep the type as restrictive as
  15318. possible.) In this case, the functions fgetpos and fsetpos offer no advantage
  15319. over the older file-positioning functions. The difference can be important for
  15320. ther systems, however.
  15321. _Fgpos is simpler under UNIX in another way. No mapping occurs between the
  15322. internal and external forms of text streams. Hence, the correction for
  15323. characters in internal buffers is simple. Consider, by comparison, a system
  15324. that maps text streams. Say it terminates each text line with a carriage
  15325. return plus line feed instead of just a line feed. That means that _Fread must
  15326. discard certain carriage returns and _Fwrite must insert them. It also means
  15327. that _Fgpos must correct for any alterations when it corrects the
  15328. file-position indicator. The problem is manageable, but it leads to messy
  15329. logic that I choose not to show at this point.
  15330. Listing 6 shows the file xfspos.c that defines the function _Fspos. It too
  15331. benefits from the simple UNIX I/O model in the same ways as _Fgpos. Output
  15332. causes no problems, since the function flushes any unwritten characters before
  15333. it alters the file-position indicator.
  15334. The remaining three primitives are macros. All expand to calls on functions
  15335. that perform UNIX system services directly. The UNIX version of "yfuns.h"
  15336. contains the lines:
  15337. #define _Fclose(str) \ _Close((str)->_Handle)
  15338. #define _Fread(str, buf, cnt) \
  15339. _Read((str)->_Handle, buf, cnt)
  15340. #define _Fwrite(str, buf, cnt) \
  15341. _Write((str)->_Handle, buf, cnt)
  15342. int _Close(int);
  15343. int _Read(int, unsigned char *, int);
  15344. int _Write(int, const unsigned char *, int);
  15345.  
  15346.  
  15347.  
  15348. Summary
  15349.  
  15350.  
  15351. That's the underpinnings of this implementation of the header <stdio.h>. I can
  15352. now go on to show you how to perform various unformatted reads and writes
  15353. using this machinery. And as a grand finale, you can see at least some of the
  15354. code needed to perform formatted I/O. The story continues.
  15355. This article is excerpted in part from P.J. Plauger, The Standard C Library,
  15356. (Englewood Cliffs, N.J.: Prentice-Hall, 1992).
  15357.  
  15358. Listing 1 (remove.c)
  15359. /* remove function -- UNIX version */
  15360. #include "xstdio.h"
  15361.  
  15362. /* UNIX system call */
  15363. int _Unlink(const char *);
  15364.  
  15365. int (remove)(const char *fname)
  15366. { /* remove a file */
  15367. return (_Unlink(fname));
  15368. }
  15369.  
  15370. /* End of File */
  15371.  
  15372.  
  15373. Listing 2 (rename.c)
  15374. /* rename function -- UNIX version */
  15375. #include "xstdio.h"
  15376.  
  15377. /* UNIX system calls */
  15378. int _Link(const char * const char *);
  15379. int _Unlink(const char *);
  15380.  
  15381. int (rename)(const char *old, const char *new)
  15382. { /* rename a file */
  15383. return (_Link(old, new) ? -1 : _Unlink(old));
  15384. }
  15385.  
  15386. /* End of File */
  15387.  
  15388.  
  15389. Listing 3 (tmpnam.c)
  15390. /* tmpnam function -- UNIX version */
  15391. #include <string.h>
  15392. #include "xstdio.h"
  15393.  
  15394. /* UNIX system call */
  15395. int._Getpid(void);
  15396.  
  15397. char *(tmpnam)(char*s)
  15398. { /* create a temporary file name */
  15399. int i;
  15400. char *p;
  15401. unsigned short t;
  15402. static char buf[L_tmpnam];
  15403. static unsigned short seed = 0;
  15404.  
  15405. if (s == NULL)
  15406. s = bur;
  15407. seed = seed == 0 ? _Getpid() : seed + 1;
  15408. strcpy(s, "/tmp/t");
  15409. i = 5;
  15410.  
  15411. p = s + strlen(s) + i;
  15412. *p = '\0';
  15413. for (t = seed; 0 <= --i; t >>= 3)
  15414. *--p = '0' + (t & 07);
  15415. return (s);
  15416. }
  15417.  
  15418. /* End of File */
  15419.  
  15420.  
  15421. Listing 4 (xfopen.c)
  15422. /* _Fopen function -- UNIX version */
  15423. #include "xstdio.h"
  15424.  
  15425. /* UNIX system call */
  15426. int _Open(const char *, int, int);
  15427.  
  15428. int _Fopen(const char *path, unsigned int smode,
  15429. const char *muds)
  15430. { /* open from a file */
  15431. unsigned int acc;
  15432.  
  15433. acc = (smode & (_MOPENR_MOPENW)) ==
  15434. (_MOPENR_MOPENW) ? 2
  15435. : smode & _MOPENW ? 1 : 0;
  15436. if (smode & _MOPENA)
  15437. acc = 010; /* 0_APPEND */
  15438. if (smode & _MTRUNC)
  15439. acc = 02000; /* 0_TRUNC */
  15440. if (smode & _MCREAT)
  15441. acc = 01000; /* 0_CREAT */
  15442. return (_Open(path, acc, 0666));
  15443. }
  15444.  
  15445. /* End of File */
  15446.  
  15447.  
  15448. Listing 5 (xfgpos.c)
  15449. /* _Fgpos function -- UNIX version */
  15450. #include <errno.h>
  15451. #include "xstdio.h"
  15452.  
  15453. /* UNIX system call */
  15454. long _Lseek(int, long, int);
  15455.  
  15456. long _Fgpos(FILE *str, fpos_t *ptr)
  15457. { /* get file position */
  15458. long loff = _Lseek(str-> _Handle, OL, 1);
  15459.  
  15460. if (loff == -1)
  15461. { /* query failed */
  15462. errno = EFPOS;
  15463. return (EOF);
  15464. )
  15465. if (str-_Mode & _MWRITE)
  15466. loff += str->_Next - str->_Buf;
  15467. else if (str->_Mode & _MREAD)
  15468. loff -= str-> Nback
  15469. ? str-> _Rsave - str-> _Next +
  15470.  
  15471. str-> _Nback
  15472. : str->_Rend - str->_Next;
  15473. if (ptr == NULL)
  15474. return (loff); /* ftell */
  15475. else
  15476. { /* fgetpos */
  15477. ptr->_Off = loff;
  15478. return (0);
  15479. }
  15480. }
  15481.  
  15482. /* End of File */
  15483.  
  15484.  
  15485. Listing 6 (xfspos.c)
  15486. /* _Fspos function -- UNIX version */
  15487. #include <errno.h>
  15488. #include "xstdio.h"
  15489.  
  15490. /* UNIX system call */
  15491. long _Lseek(int, long, int);
  15492.  
  15493. int_Fspos(FILE *str, const fpos_t *ptr, long loff, int way)
  15494. { /* position a file */
  15495. if (fflush(str))
  15496. { /* write error */
  15497. errno = EFPOS;
  15498. return (EOF);
  15499. }
  15500. if (ptr)
  15501. loff += ((fpos_t *)ptr)->_Off; /* fsetpos */
  15502. if (way == SEEK CUR && str-> _Mode & _MREAD)
  15503. loff -= str-> _Nback
  15504. ? str-> _Rsave - str-> _Next + str-
  15505. >_Nback
  15506. :str->_Rend - str-> _Next;
  15507. if (way == SEEK_CUR && loff != 0
  15508.  way != SEEK_SET loff != -1)
  15509. loff = _Lseek(str->_Handle, loff, way);
  15510. if (loff == -1)
  15511. { /* request failed */
  15512. errno = EFPOS;
  15513. return (EOF);
  15514. }
  15515. else
  15516. { /* success */
  15517. if (str->_Mode & (_MREAD _MWRITE))
  15518. { /* empty buffer */
  15519. str->_Next = str->_Buf;
  15520. str->_Rend = str->_Buf;
  15521. str->_Wend = str->_Buf;
  15522. str->_Nback = 0;
  15523. }
  15524. str->_Mode &= ~(_MEOF_MREAD_MWRITE);
  15525. return (0);
  15526. }
  15527. }
  15528.  
  15529. /* End of File */
  15530.  
  15531.  
  15532.  
  15533.  
  15534.  
  15535.  
  15536.  
  15537.  
  15538.  
  15539.  
  15540.  
  15541.  
  15542.  
  15543.  
  15544.  
  15545.  
  15546.  
  15547.  
  15548.  
  15549.  
  15550.  
  15551.  
  15552.  
  15553.  
  15554.  
  15555.  
  15556.  
  15557.  
  15558.  
  15559.  
  15560.  
  15561.  
  15562.  
  15563.  
  15564.  
  15565.  
  15566.  
  15567.  
  15568.  
  15569.  
  15570.  
  15571.  
  15572.  
  15573.  
  15574.  
  15575.  
  15576.  
  15577.  
  15578.  
  15579.  
  15580.  
  15581.  
  15582.  
  15583.  
  15584.  
  15585.  
  15586.  
  15587.  
  15588.  
  15589.  
  15590.  
  15591.  
  15592.  
  15593.  
  15594. Doctor C's Pointers(R)
  15595.  
  15596.  
  15597. Data Structures, Part 9
  15598.  
  15599.  
  15600.  
  15601.  
  15602. Rex Jaeschke
  15603.  
  15604.  
  15605. Rex Jaeschke is an independent computer consultant, author and seminar leader.
  15606. He participates in both ANSI and ISO C Standards meetings and is the editor of
  15607. The Journal of C Language Translation, a quarterly publication aimed at
  15608. implementors of C language translation tools. Readers are encouraged to submit
  15609. column topics and suggestions to Rex at 2051 Swans Neck Way, Reston, VA 22091
  15610. or via UUCP at rex@aussie.com.
  15611.  
  15612.  
  15613. A stack is a list in which nodes can be added only at one end. Nodes can be
  15614. accessed or removed only from that same end. When a new node is added it hides
  15615. the previous end node, so in order to access that previous node the new node
  15616. must first be removed. That is, the list works in a Last-In First-Out (LIFO)
  15617. manner. The most recently added node is the first available for use, while the
  15618. first node added is the last one available for use.
  15619. An often used example of a stack is that of a stack of plates in a cafeteria.
  15620. The plates are stacked one on top of the other inside a spring-loaded hopper.
  15621. As you take a plate off the top of the stack the weight of the whole stack
  15622. decreases and the spring raises the remaining stack up to the top of the
  15623. hopper. Since the term stack implies some sort of vertical orientation we
  15624. often talk about stacks having a top and a bottom. And the act of adding to
  15625. the top is commonly referred to as pushing onto the stack while that of
  15626. removing from the top is called popping off the stack.
  15627. A stack need not have any particular orientation, but the terms push and pop
  15628. are still typically used. For example, a paper napkin dispenser can push
  15629. backward and pop forward; while a paper cup dispenser, as found at airplane
  15630. water fountains, pushes upward and pops downward. In these mechanical stacks,
  15631. push and pop operations typically do physically move the underlying stack.
  15632. However, in a stack of memory this is not always the case. Although pushing
  15633. onto a stack in a computer program results in a new top node being added, it
  15634. typically will not affect the underlying nodes. To do so would be most
  15635. inefficient and is equivalent to inserting and deleting a node from the
  15636. beginning of an array. 
  15637.  
  15638.  
  15639. Some Applications
  15640.  
  15641.  
  15642. Like other kinds of data structures, a stack has its strengths and weaknesses.
  15643. When comparing one method of representation over another you must look at the
  15644. kind of data being stored as well as the methods in which you will need to
  15645. access it. Apart from storing plates, just how might a stack be used? 
  15646. Applications involving tree structures can lend themselves to using stacks.
  15647. Consider a nested hierarchy that starts from the top and branches out below in
  15648. a number of directions. Each of those directions branches out into yet other
  15649. directions, etc. An example of such a model would be a program written in a
  15650. procedural language such as Fortran, COBOL, Pascal, C, or C++. The main
  15651. program calls subroutines which in turn call other subroutines, etc. A block
  15652. structured language, such as Pascal or C, also allows nesting of blocks within
  15653. a single subroutine. And C++ provides a complex data and function nesting
  15654. capability via its inheritance mechanism.
  15655. Such a language uses a stack by selecting a particular logic path in the
  15656. hierarchy and following it down to its end branches, pushing things onto the
  15657. stack as it goes. When it reaches the end of the path, it reverses direction,
  15658. popping items off the stack as it traverses the path back to the beginning,
  15659. where it takes another downward path.
  15660. Computers handle a very common problem, that of solving arithmetic
  15661. expressions, using a hierarchical organization. Consider the four binary
  15662. operators: addition (+), subtraction (-), multiplication (*), and division
  15663. (/). Mathematically, you can write arbitrarily complex expressions involving
  15664. just these operators and their operands. For example,
  15665. a + b - c * d + e / f - g
  15666. Just as in mathematics, computer languages have rules for operator precedence.
  15667. In the case of C and C++, multiplication and division have the same
  15668. precedence. Their precedence is higher than that shared by addition and
  15669. subtraction. To determine the precedence of two operators at the same
  15670. precedence level, C and C++ provide the concept of associativity. Operators
  15671. with the same precedence associate left to right. Therefore, in C and C++, the
  15672. grouping of terms in the previous expression is interpreted as
  15673. ((((a + b) - (c * d)) + (e / f)) - g)
  15674. The tree in Figure 1 represents this expression, clearly showing the
  15675. hierarchy.
  15676. To evaluate such as expression, a C or C++ program needs to get to the leaves
  15677. at the bottom of the branches. Going from left to right, it first evaluates a
  15678. + b then saves the result in some temporary location. Next it evaluates c + d
  15679. and subtracts the result from the value stored in the temporary location. It
  15680. then stores the new result back in the temporary location. It continues to
  15681. traverse the tree like this until it puts the final result back in the
  15682. temporary location.
  15683.  
  15684.  
  15685. A Primitive Expression Evaluator
  15686.  
  15687.  
  15688. A C or C++ program can implement such an expression evaluator using a stack.
  15689. It first pushes both operands on the stack, pops them off to form the result
  15690. after applying their operator, and pushes that result back on the stack where
  15691. it can be used later. So the value on the top of the stack represents the
  15692. result of the expression processed thus far.
  15693. A study of a primitive implementation of an expression evaluator begins with a
  15694. look at the stack manipulation functions as shown in Listing 1.
  15695. Assume all terms have values that can be represented correctly in a signed
  15696. int. As such, the stack is an array of int and the size of the stack is
  15697. determined by the macro STACK_TOP. The variable stack_ptr maintains the index
  15698. of the next available slot on the stack. (It takes its name from the Stack
  15699. Pointer, a special purpose hardware register used in many CPUs to maintain the
  15700. current stack top.) Since the stack is initially empty, stack_ptr is
  15701. initialized to 0. The type of stack_ptr is size_t, an unsigned integer type
  15702. defined in stdio.h and other headers. Because the stack is not very large, the
  15703. type int could just have easily been used. However, using size_t makes the
  15704. solution maximally robust regardless of the value of STACK_TOP.
  15705. Because the stack need only be directly accessed by the functions push () and
  15706. pop (), it, along with the stack pointer, is static. This allows these two
  15707. objects and two functions to be isolated from other source modules.
  15708. To push a value on the stack, a C or C++ program first checks to see if the
  15709. stack is full. If the stack is not full, the program pushes the new value on
  15710. the stack and increments the stack pointer. Otherwise, it complains that the
  15711. stack is full.
  15712. To pop a value off the stack, a C or C++ program first checks to see if the
  15713. stack is empty. If the stack is not empty, it removes the value and decrements
  15714. the stack pointer. Note that when popping, a program does not actually remove
  15715. a value from the array; it simply adjusts the stack pointer so that subsequent
  15716. push operations can overwrite the values previously popped. If the stack is
  15717. empty, the program complains, returning a dummy value of zero.
  15718. The four functions in Listing 2 correspond to the four operators in Listing 1.
  15719. Addition and multiplication are almost identical and they are the simplest.
  15720. When applying an operator, a C or C++ program assumes that the two operands
  15721. have already been pushed on the stack, left operand first. So to add two
  15722. values, the program simply pops them off the stack, adds them together, and
  15723. pushes the result back on the stack. Note that the order of evaluation of the
  15724. two terms across the four binary operators is unspecified by C. That is, in
  15725. pop() + pop(), it doesn't know which call to pop () will be made first. It so
  15726. happens, that it doesn't matter for addition and multiplication, since these
  15727. operators are commutative. That is, a + b is equivalent to b + a, and likewise
  15728. for a * b and b * a.
  15729. Because subtraction and division are not commutative, a program must know
  15730. exactly which operand it is getting when calling pop(). To do this, it needs
  15731. to have a sequence point between the two calls to pop(). The most common way
  15732. to achieve this is to put them into different statements. This does, however,
  15733. require the temporary variable temp.
  15734. A useful feature for divide would be to check for a zero denominator and, on
  15735. finding one, complain and return a zero value just like in the case of stack
  15736. underflow.
  15737. Listing 3 contains the main program that exercises these functions.
  15738. Some sample inputs and outputs are
  15739. Enter expression: 12 + 56
  15740. Result = 68
  15741. Enter expression: 23 - 40
  15742. Result = -17
  15743. Enter expression: 123 * 12
  15744. Result = 1476
  15745. Enter expression: 12345 / 7
  15746. Result = 1763
  15747. You might think the four operator functions are trivial enough they could be
  15748. implemented as macros. The most obvious attempt at this results in something
  15749. like Listing 4.
  15750.  
  15751. The problem comes with subtract () and divide() where you need a temporary
  15752. int. To get this you must make the macro definition a block and that precludes
  15753. its use from a number of common places, including the main program in Listing
  15754. 3. The fragment involving the call to subtract
  15755. else if (op[0] == '-')
  15756. subtract();
  15757. else if (op[0] == '*')
  15758. expands to the code in Listing 5, resulting in the true path of the if having
  15759. two statements -- a block statement and a null statement. As such, the
  15760. compiler complains about the dangling else that follows.
  15761. The program in Listing 3 doesn't actually evaluate nested expressions, just
  15762. one pair of operands and an operator at a time. The program in Listing 6 does
  15763. more but only for a specific instance of the expression
  15764. (a + b) * (c - d);
  15765.  
  15766.  
  15767. Tokenizers And Parsers
  15768.  
  15769.  
  15770. Handling arbitrarily complex expressions, as does a compiler or interpreter,
  15771. requires machinery beyond the scope of this column. For example, it requires a
  15772. routine to break free-form source into tokens. Such a routine is often called
  15773. a tokenizer. While it is not too difficult to write your own, the lex utility
  15774. (available with UNIX systems, as a third-party tool from MKS and others) and
  15775. the public domain version Flex, can be used to automatically generate
  15776. tokenizers.
  15777. To handle all binary, unary, and ternary operators in arbitrarily complex
  15778. expressions we need a parser. And again, while you could write your own, yacc
  15779. is a commonly available parser generator (as are GNU's bison and Holub's occs,
  15780. among others).
  15781. If you have interest in pursuing tokenizing and parsing further you may wish
  15782. to refer to the book The UNIX Programming Environment from Prentice-Hall by
  15783. Kernighan and Pike. The whole of chapter 8 is dedicated to using these tools
  15784. to implement a quite powerful calculator called hoc. And it uses a stack with
  15785. push () and pop () functions along the way.
  15786.  
  15787.  
  15788. Hardware Stacks
  15789.  
  15790.  
  15791. Many computers have one or more stacks implemented in hardware. These are used
  15792. to pass information into, and sometimes back from, a function. A common
  15793. implementation of C involves allocating all automatic data on such a stack on
  15794. entry to its parent function. In such cases though, the variables are not
  15795. popped off the stack each time they are used. Instead, the compiler generates
  15796. references to them using the stack pointer and an offset. For example, when
  15797. the program
  15798. void f(void)
  15799. {
  15800. char c[5];
  15801. int i = 3;
  15802. unsigned int u = 4;
  15803.  
  15804. c[1] = 'A';
  15805. c[3] = 'B';
  15806. }
  15807. is compiled by Borland's C compiler, the relevant part of the code generated
  15808. is shown in Listing 7.
  15809. Because automatic variables are often stored on a stack some interesting
  15810. things can happen. Consider the program in Listing 8.
  15811. The function f() is called three times in succession and each time on entry,
  15812. the compiler allocates space on the stack for j and frees it on exit. It is
  15813. very likely in this case that each time j is allocated, it occupies the exact
  15814. same location in memory and so the contents it had from the previous iteration
  15815. will be seen at the start of the next. As such, the output resulting could be
  15816. j = 110
  15817. j = 111
  15818. j = 112
  15819. Note though that when automatic data is allocated on the stack, it has no
  15820. guaranteed default initial value. That is, while space is allocated on the
  15821. stack for j it isn't really pushed. That would have resulted in an explicit
  15822. value being placed there.
  15823. A more interesting possibility is the following
  15824. j = 0
  15825. j = 1
  15826. j = 2
  15827. Here the garbage initial value just happens to be zero, as if j had been
  15828. static.
  15829. A smarter compiler would likely recognize that it can throw away the increment
  15830. to j since j is automatic and, as such, does not retain its value across
  15831. function calls. This then would result in something like
  15832. j = 1515
  15833. j = 1515
  15834. j = 1515
  15835. As we have said, even though values are popped off a stack, they still
  15836. actually reside there. So when that stack space is allocated for another
  15837. purpose, unless it is initialized, it will take on the residual value left
  15838. from previous uses. And this can give rise to some interesting addresses when
  15839. you allocate an automatic pointer without initializing it.
  15840.  
  15841.  
  15842. Other Considerations
  15843.  
  15844.  
  15845. The above solutions and discussion skip over or bypass a number of important
  15846. considerations. First, the stack is limited to ints. In reality we would want
  15847. something else. Certainly, an array of doubles significantly increases the
  15848. possibilities, but it would lose precision on large integers. Changing push ()
  15849. and pop() to handle double values is trivial. Try it. You might also think
  15850. about having an array of unions where the union's member set comprises all the
  15851. types you intend to store on the stack. You might also find it useful to
  15852. record the current type stored in the union as well.
  15853. By implementing the stack as an array you have a fixed limit on its size. And
  15854. while you could obtain the array at runtime using malloc(), it still might be
  15855. useful to have a more general solution. For example, you could implement a
  15856. stack as a singly-linked list. The root always points to the top node and each
  15857. node points forwards to the next. The stack pointer would then be the address
  15858. of the next available node.
  15859. When using a linked list, it might be inefficient to actually allocate a node
  15860. on each push request and free one on each pop. It might be better to only
  15861. allocate a new node when the current list is full but never free any. You
  15862. might also wish to allocate nodes in clusters rather than to do them one at a
  15863. time.
  15864. Whatever approach you take to representing the stack, you should always have a
  15865. way of detecting and handling stack overflow and underflow. In the DOS world,
  15866. it is common to find C compilers calling a stack probe routine immediately on
  15867. entry to a C function. The probe checks to see if allocating the automatic
  15868. variables needed by this function will overflow the stack. If it will, the
  15869. probe function terminates the program with a message to that effect. If it
  15870. will not overflow, the probe returns, the space is allocated, and the program
  15871. continues. If you disable this stack probe via a compiler option or a #pragma
  15872. directive, watch out, since now stack overflow may well cause your system to
  15873. hang.
  15874. Figure 1
  15875.  
  15876. Listing 1
  15877.  
  15878. #include <stdio.h>
  15879.  
  15880. #define STACK_TOP 50
  15881.  
  15882. static int stack[STACK_TOP];
  15883. static size_t stack_ptr = 0;
  15884.  
  15885. void push(int value)
  15886. {
  15887. if (stack_ptr < STACK_TOP)
  15888. stack[stack_ptr++] = value;
  15889. else
  15890. fprintf(stderr, "Stack full: discarding
  15891. %d\n", value);
  15892. }
  15893.  
  15894. int pop(void)
  15895. {
  15896. if (stack_ptr > 0)
  15897. return stack[--stack_ptr];
  15898. else {
  15899. fprintf(stderr, "Stack empty\n");
  15900. return 0;
  15901. }
  15902. }
  15903.  
  15904. /* End of File */
  15905.  
  15906.  
  15907. Listing 2
  15908. void add(void) { push(pop() + pop()); }
  15909. void subtract(void) { int temp = pop(); push(pop() - temp); }
  15910. void multiply(void) { push(pop() * pop()); }
  15911. void divide(void) { int temp = pop(); push(pop() / temp); }
  15912.  
  15913. /* End of File */
  15914.  
  15915.  
  15916. Listing 3
  15917. main()
  15918. {
  15919. char op[2];
  15920. int i1, i2;
  15921.  
  15922. while(1) {
  15923. printf("Enter expression: ");
  15924. if (scanf("%d %s %d", &i1, op, &i2) == EOF)
  15925. break;
  15926.  
  15927. push(i1);
  15928. push(i2);
  15929.  
  15930. if (op[0] == '+')
  15931. add();
  15932. else if (op[0] == '-')
  15933. subtract();
  15934. else if (op[0] == '*')
  15935. multiply();
  15936. else if (op[0] == '/')
  15937.  
  15938. divide();
  15939. else {
  15940. fprintf(stderr, "Unknown operator >%s<\n", op);
  15941. pop(); /* discard both operands */
  15942. pop();
  15943. push(0); /* substitute a zero result */
  15944. }
  15945.  
  15946. printf("Result = %d\n", pop());
  15947. }
  15948.  
  15949. return 0;
  15950. }
  15951. /* End of File */
  15952.  
  15953.  
  15954. Listing 4
  15955. #define add() (push(pop() + pop()))
  15956. #define subtract() {int temp = pop(); (push(pop() - temp))}
  15957. #define multiply() (push(pop() * pop()))
  15958. #define divide() {int temp = pop(); (push(pop() / temp))}
  15959.  
  15960. /* End of File */
  15961.  
  15962.  
  15963. Listing 5
  15964. else if (op[0] == '-')
  15965. {int temp = pop(); (push(pop() - temp))};
  15966. else if (op[0] == '*')
  15967.  
  15968. /* End of File */
  15969.  
  15970.  
  15971. Listing 6
  15972. #include <stdio.h>
  15973.  
  15974. main()
  15975. {
  15976. int a = 8, b = 2, c = 6, d = 3;
  15977.  
  15978. push(a);
  15979. push(b);
  15980. add(); /* t1 = a + b */
  15981. push(c);
  15982. push(d);
  15983. subtract(); /* t2 = c - d */
  15984. multiply(); /* t3 = t1 * t2 */
  15985.  
  15986. printf("Result = %d\n", pop());
  15987.  
  15988. return 0;
  15989. }
  15990.  
  15991. Result = 30
  15992.  
  15993. /* End of File */
  15994.  
  15995.  
  15996. Listing 7
  15997.  
  15998. ; void f(void)
  15999.  
  16000. _f push bp ; save base ptr
  16001. mov bp,sp ; copy stack ptr to bp
  16002. sub sp,10 ; allocate 10 bytes for autos
  16003.  
  16004. ; {
  16005. ; char c[5];
  16006. ; int i = 3;
  16007.  
  16008. mov word ptr [bp-8],3 ; access i
  16009.  
  16010. ; unsigned int u = 4;
  16011.  
  16012. mov word ptr [bp-10],4 ; access u
  16013.  
  16014. ; c[1] = 'A';
  16015.  
  16016. mov byte ptr [bp-5],65 ; access c[1]
  16017.  
  16018. ; c[3] = 'B';
  16019.  
  16020. mov byte ptr [bp-3],66 ; access c[1]
  16021.  
  16022. ; }
  16023.  
  16024. mov sp,bp ; throw away 10 bytes
  16025. pop bp ; restore bp saved on entry
  16026. ret
  16027. _f endp
  16028.  
  16029. /* End of File */
  16030.  
  16031.  
  16032. Listing 8
  16033.  
  16034. #include <stdio.h>
  16035.  
  16036. void f(void);
  16037.  
  16038. main()
  16039. {
  16040. int i;
  16041.  
  16042. for (i = 0; i <= 2; ++i)
  16043. f();
  16044. }
  16045.  
  16046. void f(void)
  16047. {
  16048. int j;
  16049.  
  16050. printf("j = %d\n", j);
  16051. ++j;
  16052. }
  16053.  
  16054. /* End of File */
  16055.  
  16056.  
  16057.  
  16058.  
  16059.  
  16060.  
  16061.  
  16062.  
  16063.  
  16064.  
  16065.  
  16066.  
  16067.  
  16068.  
  16069.  
  16070.  
  16071.  
  16072.  
  16073.  
  16074.  
  16075.  
  16076.  
  16077.  
  16078.  
  16079.  
  16080.  
  16081.  
  16082.  
  16083.  
  16084.  
  16085.  
  16086.  
  16087.  
  16088.  
  16089.  
  16090.  
  16091.  
  16092.  
  16093.  
  16094.  
  16095.  
  16096.  
  16097.  
  16098.  
  16099.  
  16100.  
  16101.  
  16102.  
  16103.  
  16104.  
  16105.  
  16106.  
  16107.  
  16108.  
  16109.  
  16110.  
  16111.  
  16112.  
  16113.  
  16114.  
  16115.  
  16116.  
  16117.  
  16118.  
  16119.  
  16120.  
  16121. Questions & Answers
  16122.  
  16123.  
  16124. Using The Conditional Operator ?:
  16125.  
  16126.  
  16127.  
  16128.  
  16129. Ken Pugh
  16130.  
  16131.  
  16132. Kenneth Pugh, a principal in Pugh-Killeen Associates, teaches C language
  16133. courses for corporations. He is the author of C Language for Programmers and
  16134. All On C, and was a member on the ANSI C committee. He also does custom C
  16135. programming for communications, graphics, image databases, and hypertext. His
  16136. address is 4201 University Dr., Suite 102, Durham, NC 27707. You may fax
  16137. questions for Ken to (919) 489-5239. Ken also receives email at
  16138. kpugh@dukemvs.ac.duke.edu (Internet).
  16139.  
  16140.  
  16141. Q
  16142. Our company has banned the use of the conditional expression operator? :.
  16143. What's wrong with it?
  16144. Bill Malone
  16145. Worcester, MA
  16146. A
  16147. This same question has floated around Usenet. As I see it, the problem is not
  16148. with the operator itself, but with the potential for combining it into
  16149. expressions which become indecipherable.
  16150. The operator itself is not necessarily bad. In my C classes, I usually suggest
  16151. avoiding it as there is the potential for confusion. I give the students an
  16152. example such as:
  16153. i = (7 > 5 ? (10 > 9 ? 4 : 7)
  16154. : (3 > 1 ? 6 : 2);
  16155. Most of the students take a few minutes to determine the value assigned to i.
  16156. The solution can be found using ifs in only few seconds.
  16157. if (7 > 5)
  16158. if (10 > 9)
  16159. i = 4;
  16160. else
  16161. i =7;
  16162. else
  16163. if (3 > 1)
  16164. i = 6;
  16165. else
  16166. i = 2;
  16167. The feeling of complexity is not universal. A group that I taught at Lotus
  16168. found the conditional operator rather simple, since its form matches that used
  16169. by 1-2-3 formulas.
  16170. I once did a port of a C program from an IBM-PC to an Apple II. As the program
  16171. was compiling, it aborted with an error message that it was out of expression
  16172. space. The problem was with an expression that used the conditional operator.
  16173. The creator of the program must have fallen in love with this operator. The
  16174. expression had the operator nested ten deep. Luckily the compiler had an
  16175. option to increase the expression space and I did not have to rewrite the
  16176. statement.
  16177. One example of the conditional operator that was discussed on the net looked
  16178. like:
  16179. strcpy (foo, x ? xvar : yvar);
  16180. This was deemed preferable by some to:
  16181. if (x)
  16182. strcpy (foo, xvar);
  16183. else
  16184. strcpy (foo, yvar);
  16185. Another example on the net looked something like Listing 1. Some responders
  16186. liked the compactness of this statement. A proposed alternative for code
  16187. similar to Listing 1 was code like that seen in Listing 2.
  16188. Some people felt that Listing 2 was too wordy and had other problems. However,
  16189. I would propose an even more complicated, but more maintainable organization
  16190. (to my mind). This is shown in Listing 3.
  16191. I feel Listing 3 is more maintainable for several reasons. First, the values
  16192. of the string might be used in more than one place in the program. Keeping
  16193. them in a data item makes them accessible to any statement that needs them.
  16194. Second, this is closer to the organization which most windowed systems now
  16195. employ. There is a separation of data from logic. The strings themselves could
  16196. be read in from a resource file, if that were available. Notice that if you
  16197. keep all your strings in the declaration portion of the program, a maintainer
  16198. can easily find them for translation into another language or simply another
  16199. phrase in the same language.
  16200. The arguments on the net ranged around the ideas that "if it's in C, then a C
  16201. programmer should use it. If you don't understand it, then you shouldn't be
  16202. programming in C". My response to that is that I can create monsters of
  16203. expressions that are indecipherable using C operators. Just because a feature
  16204. is there does not mean I have to abuse it.
  16205. Q
  16206. The K&R Book says (on page 37) that all automatic variables are initialized
  16207. each time the function is entered. I have a piece of a program below in which
  16208. p is initialized only once. Please explain what I am missing here.
  16209. The answer that I can think of is that the following two declarations
  16210. char *p = "Old Value" ;
  16211. and
  16212. static char *p = "Old Value" ;
  16213. are equivalent. Are they really? Also this behavior is not observed for other
  16214. types of pointers.
  16215. Abhay B. Joshi
  16216. Syracuse, NY
  16217.  
  16218. A
  16219. The two declarations are sort of equivalent. Regardless of which one you use
  16220. you will get the same value. The variable p is a pointer to char. The string
  16221. "Old Value" is a string constant, or as I like to describe it, an unnamed
  16222. array of chars identified by an address.
  16223. When you initialize a pointer to char with a string, the address of that array
  16224. of char is stored into the pointer. The difference between your static and
  16225. non-static declarations is that this initialization takes place either every
  16226. time the function is entered (for the automatic variable) or just once (for
  16227. the static variable). If your function looked like
  16228. example_function()
  16229. {
  16230. char *p = "Old Value" ;
  16231. static char *ps = "Old Value";
  16232. printf("p is %s ps is %s", p, ps);
  16233. p = "Another Value";
  16234. ps = "Another Value";
  16235. }
  16236. then the first time through it would print Old Value for both variables. Each
  16237. subsequent time, it would print Old Value for p and Another Value for ps.
  16238. Now let's take a look at what your main function is doing. getstr() returns
  16239. the value of p, which contains the address of Old Value. You perform a strcpy.
  16240. This copies New Value to the address where Old Value is stored. You have wiped
  16241. out the constant string.
  16242. The next time getstr() returns, it returns the same address. But the contents
  16243. of that address is now New Value.
  16244. Q
  16245. How does one truncate a file under UNIX. I'm looking for a routine similar to
  16246. DOS's chsize() routine that will allow me to set the size of a file. The size
  16247. that I pass to the function needs to be smaller than the length of the file
  16248. itself (i.e., shorten it)
  16249. Would appreciate any snippets of code, etc. Thanks.
  16250. Brooks Cutter
  16251. Largo, FL
  16252. A
  16253. BSD UNIX has ftruncate(fd, length), which works just like chsize( ). It also
  16254. has truncate(path, length), which applies to a non-opened file. However, if
  16255. you are using System V, that doesn't help too much. I suggest something like
  16256. Listing 5, which works on a non-opened file. If you do not need to worry about
  16257. multiple links to the file, then you could simplify it by eliminating the copy
  16258. back to the original file. You would unlink the original file and then rename
  16259. the temporary file.
  16260. To avoid a lot of nested logic, I have used gotos. gotoless coders are invited
  16261. to send in their own version. Using multiple return statements counts the same
  16262. in my book as using gotos.
  16263. Q
  16264. In the report provided byCHKDSK there is something called "Volume Serial
  16265. Number". The format utility also reports this same thing and evidently format
  16266. creates a new volume serial number each time a disk is formatted. My question
  16267. is this, where is that serial number stored and what is its intended use?
  16268. Rob Buck
  16269. Fairfield, IA
  16270. A
  16271. I can give you an exact quote from, the MS-DOS Programmer's Reference Manual.
  16272. I couldn't say it better myself.
  16273. "To help distinguish one removable disk or tape from another, the format
  16274. command creates a unique identifier for each volume... as it formats the
  16275. volume. Programs can also create their own unique identifiers by using Set
  16276. Media ID (Interrupt 21h Function 440Dh Minor Code 46h) to set the volume
  16277. label, volume serial number, and file-system type.
  16278. "Since the user can change the volume in a removable-media drive at any time,
  16279. programs that read from or write to removable media need ways to prevent
  16280. inadvertently reading from or writing to the wrong volume. Some drives have
  16281. change-line capability that helps MS-DOS automatically detect media changes...
  16282. If a drive does not have change-line capability, MS-DOS checks for the proper
  16283. volume before read and write operations"
  16284. I'm not sure which versions of MS-DOS really do this volume checking. Version
  16285. 3.2 does not (at least how I would expect). You can do a simple check by
  16286. typing
  16287. more < a:long_file
  16288. At the pause, remove the disk and put in a different one. You should get
  16289. either garbage (after a few more screenfuls) or a warning message. Version 3.2
  16290. gave me garbage.
  16291.  
  16292.  
  16293. Readers' Responses
  16294.  
  16295.  
  16296.  
  16297.  
  16298. floats And doubles
  16299.  
  16300.  
  16301. I just finished reading your response to my previous note in the September C
  16302. Users Journal, and I'm afraid we're not communicating. Consider the following
  16303. code
  16304. sub1()
  16305. {
  16306. float f;
  16307. sub2(f);
  16308. }
  16309. sub2(newf)
  16310. float newf;
  16311. {
  16312. sub3(&newf);
  16313. }
  16314. Now, according to both K&R and ANSI, when sub1 calls sub2, f is converted from
  16315. a float to a double and the double is passed to sub2. The key question is --
  16316. exactly what happens in sub2?
  16317. The declaration of newf is float, but the parameter being passed is actually a
  16318. double, so what happens? K&R says that the declaration is "adjusted to read
  16319. double." Thus, despite the fact that the programmer declared newf as a float,
  16320. the compiler takes it upon itself to quietly "adjust" that declaration and
  16321. thus make newf a double instead of a float.
  16322. In this case, sub3 gets passed a pointer to double which is clearly not what
  16323. the programmer intended. A number of compiler implementers adopted an
  16324. alternative solution to the conflict. Rather than adjusting the declaration of
  16325. new, they actually convert the double parameter back into a float.
  16326. That is, they behave as if the function had been written,
  16327. sub2(temp)
  16328.  
  16329. double temp;
  16330. {
  16331. float newf = temp;
  16332. sub3(&newf);
  16333. }
  16334. In this case sub3 is passed a pointer to float as the programmer intended, but
  16335. there may be extra work involved in converting the double back into a float.
  16336. (There also may not be. Some floating point formats allow a double to be
  16337. "converted" to a float by just ignoring the extra bytes.)
  16338. Since this approach seems to be more faithful to what the programmer asked
  16339. for, it is the one that was adopted for the ANSI standard. (Although I believe
  16340. that the "asif" rule would still allow the adjustment as long as the variable
  16341. never has its address taken.)
  16342. Larry Jones
  16343. Milford, OH
  16344. I think we are in agreement on this, except for a particular compiler's
  16345. implementation of K&R. The question revolved around the Microsoft compiler.
  16346. For your example, which uses the K&R style function header, Microsoft treats
  16347. the parameter declaration as if it were stated as double. The variable newf is
  16348. eight bytes long.
  16349. If you declare it with the new style function header as:
  16350. sub2(float newf)
  16351. {
  16352. sub3(&newf);
  16353. }
  16354. it would be truly a float.
  16355. In this case, you would need to include a prototype for sub2 for the calling
  16356. routine, e.g.:
  16357. int sub2(float newf);
  16358. This would prevent the default widening of f in sub1. If the prototype is not
  16359. there, then the function may fail (ANSI Rationale 3.3.2.2).
  16360. As stated in the Rationale section 3.7.1, this type rewriting of parameters
  16361. from float to double is no longer permissible. However Microsoft appears to
  16362. have left the old style alone. This is non-ANSI behavior and should not be
  16363. allowed if the compiler is run in ANSI-compliant mode. (KP)
  16364.  
  16365. Listing 1
  16366. printf("The value is '%s' \n", (x == 0 ? "zero" :
  16367. (x == 1 ? "one" :
  16368. (x == 2 ? "two" :
  16369. "lots")));
  16370.  
  16371. /* End of File */
  16372.  
  16373.  
  16374. Listing 2
  16375. char *out_string;
  16376. switch (x)
  16377. {
  16378. case 0:
  16379. out_string = "zero";
  16380. break;
  16381. case 1:
  16382. out_string = "one" ;
  16383. break;
  16384. case 2:
  16385. out_string = "two" ;
  16386. break;
  16387. default:
  16388. out_string = "lots";
  16389. break;
  16390. }
  16391.  
  16392. printf ("The value is '%s'\n", out_string)
  16393.  
  16394. /* End of File */
  16395.  
  16396.  
  16397. Listing 3
  16398. int index;
  16399. #define SIZE_OUT_STRINGS 4
  16400.  
  16401. char *out_strings[SIZE_OUT_STRINGS]
  16402. = {"zero", "one", "two", "lots"};
  16403.  
  16404.  
  16405. if (x < 0 x >= SIZE_OUT_STRINGS - 1)
  16406. index = SIZE_OUT STRINGS - 1;
  16407. else
  16408. index = x;
  16409. printf ("The value is '%s'\n", out_strings[index]);
  16410.  
  16411. /* End of File */
  16412.  
  16413.  
  16414. Listing 4
  16415. char *getstr();
  16416. main()
  16417. {
  16418. char *str ;
  16419. printf("%s\n",str = getstr()) ; /* prints "Old Value" */
  16420. strcpy(str,"New Value") ;
  16421. printf("%s\n",str = getstr()) ; /* prints "New Value" */
  16422. }
  16423.  
  16424. char *getstr()
  16425. {
  16426. char *p = "Old Value" ;
  16427. return p ;
  16428. }
  16429.  
  16430. /* End of File */
  16431.  
  16432.  
  16433. Listing 5
  16434. #include <stdio.h>
  16435. #define SIZE_BUFFER 1024
  16436. #include <sys\types.h>
  16437. #include <sys\stat.h>
  16438. #ifdef BSD
  16439. #include <sys\file.h> /* This header name varies */
  16440. #else
  16441. #include <fcntl.h>
  16442. #endif
  16443. static int copy_files();
  16444.  
  16445. truncate_file(path, length)
  16446. /* Truncates a file to the length specified */
  16447. char *path;
  16448. int length;
  16449. {
  16450. int input_file, temp_file;
  16451. char temp_file_name[L_tmpnam];
  16452. struct stat file_status;
  16453. int ret;
  16454.  
  16455. ret = 0;
  16456. if (length < 0)
  16457. {
  16458. ret = -10;
  16459. goto end;
  16460. }
  16461.  
  16462. /* If 0 length, then just open/close it */
  16463.  
  16464. if (length == 0)
  16465. {
  16466. input_file = open(path, 0_WRONLY 0_TRUNC, 0666);
  16467. if (input_file < 0)
  16468. {
  16469. ret = -1;
  16470. goto end;
  16471. }
  16472. close (input_file);
  16473. goto end;
  16474. }
  16475.  
  16476. input_file = open(path, 0_RDONLY, 0);
  16477. if (input_file < 0)
  16478. {
  16479. ret = -1;
  16480. goto end;
  16481. }
  16482.  
  16483. /* Check the file length - no sense copying if shorter already */
  16484. fstat(input_file, &file_status);
  16485. if (length > file_status.st_size)
  16486. {
  16487. close (input_file);
  16488. ret = -3;
  16489. goto end;
  16490. }
  16491.  
  16492. /* Open a temporary file to hold the contents to be copied back */
  16493. tmpnam(temp_file_name);
  16494. temp_file = open(temp_file_name, 0_RDWR 0_CREAT, 0666);
  16495. if (temp_file < 0)
  16496. {
  16497. ret = -2;
  16498. goto end;
  16499. }
  16500.  
  16501. /* Make a copy of the file */
  16502. ret = copy_files(input_file, temp_file, length);
  16503. if (ret == 0)
  16504. {
  16505. lseek(temp_file,0L,0);
  16506. close(input_file);
  16507. input_file = open(path, 0_WRONLY 0_TRUNC, 0666);
  16508. if (input_file < 0)
  16509. ret = -12;
  16510. else
  16511. {
  16512. ret = copy_files(temp_file, input_file, length);
  16513. close(input_file);
  16514. }
  16515. }
  16516.  
  16517. close (temp_file);
  16518. unlink(temp_file_name);
  16519.  
  16520. end:
  16521. return ret;
  16522. }
  16523.  
  16524.  
  16525. static int copy_files(file_in, file_out, length)
  16526. /* Copies from file in to file out */
  16527. int file_in;
  16528. int file_out;
  16529. int length;
  16530. {
  16531. int ret;
  16532. char buffer[SIZE_BUFFER];
  16533. int read_length;
  16534. int write_length;
  16535. int length_to_read;
  16536. int length_to_write;
  16537. ret = 0;
  16538.  
  16539. while (length > 0)
  16540. {
  16541. if (length > SIZE_BUFFER)
  16542. length_to_read = SIZE_BUFFER;
  16543. else
  16544. length_to_read = length;
  16545. length -= length_to_read;
  16546. read_length = read(file_in, buffer, length_to_read);
  16547. if (read_length != length_to_read)
  16548. {
  16549. ret = -4;
  16550. goto end;
  16551. }
  16552. length_to_write = read_length;
  16553. write_length = write(file_out, buffer,
  16554. length_to_write);
  16555. if (write_length != length_to_write)
  16556. {
  16557. ret = -5;
  16558. goto end;
  16559. }
  16560. }
  16561. end:
  16562. return ret;
  16563. }
  16564.  
  16565. main()
  16566. {
  16567. printf("\n Ret is %d", truncate_file("a:temp", 1));
  16568. }
  16569.  
  16570. /* End of File */
  16571.  
  16572.  
  16573.  
  16574.  
  16575.  
  16576.  
  16577.  
  16578.  
  16579.  
  16580.  
  16581.  
  16582.  
  16583.  
  16584.  
  16585.  
  16586.  
  16587. On The Networks
  16588.  
  16589.  
  16590. Where To Get The Sources
  16591.  
  16592.  
  16593.  
  16594.  
  16595. Sydney S. Weinstein
  16596.  
  16597.  
  16598. Sydney S. Weinstein, CDP, CCP is a consultant, columnist, lecturer, author,
  16599. professor, and president of Datacomp Systems, Inc., a consulting and contract
  16600. programming firm specializing in databases, data presentation and windowing,
  16601. transaction processing, networking, testing and test suites, and device
  16602. management for UNIX and MS-DOS. He can be contacted care of Datacomp Systems,
  16603. Inc., 3837 Byron Road, Huntingdon Valley, PA 19006-2320 or via electronic mail
  16604. on the Internet/Usenet mailbox syd@DSI.COM (dsinc!syd for those who cannot do
  16605. Internet addressing).
  16606.  
  16607.  
  16608. Another year has gone by, and once again, it's time to update my general
  16609. information column. In January 1990 (CUJ Vol. 8, No. 1), I wrote about the
  16610. internet, Internet, USENET, and Network News. In January 1991 (CUJ Vol. 9, No.
  16611. 2), I wrote about obtaining USENET Network News. Both of those two columns are
  16612. still valid, and I refer you to your libraries of back issues. (Maybe next
  16613. year it will be time to update them once again).
  16614. However, as a quick review of what was covered, this column normally reports
  16615. on freely distributable software that has recently been released via USENET
  16616. Network News. Freely distributable means that one can freely (and for free)
  16617. make copies of the software, use it as they see fit, and give it away as they
  16618. desire. It does not mean the software is in the public domain. Most of the
  16619. software is copyrighted. This means you cannot pretend you wrote it, or
  16620. include code from it in a product you are selling. However, the authors have
  16621. allowed you to use and distribute it for free. If you make changes, most
  16622. authors do not let you call the changed version by the name of the original.
  16623. This is to avoid confusion as to what is and is not part of the product and to
  16624. reduce the authors' support headaches.
  16625. The sources mentioned in this column are released via several groups
  16626. distributed as part of USENET Network News. USENET Network News performs two
  16627. roles. It is a method of distributing information among a very large group of
  16628. computers, and it is also a somewhat organized collection of that same
  16629. information into news groups that are distributed via the news software.
  16630. First, the software. The current version of the software is named C News,
  16631. because it follows A News and B News as the third rewrite of the transport
  16632. software (not because it was written in C). It supports transfer of the news
  16633. articles (the individual messages) between every member of the USENET network.
  16634. At present there are about 40,000 computers exchanging network news, and over
  16635. two million readers. News works by transmitting all articles that a site
  16636. wishes to read (its subscription list) to that site. Unlike CompuServe, or a
  16637. BBS system, users read the articles directly on their own computer. Thus you
  16638. do not need to dial into anywhere to read News, if your computer is a member
  16639. of the network, you read news directly on your own computer. (For large sites
  16640. this is a simplification, they generally designate a single computer in their
  16641. local network as the news host and then use news reader software that
  16642. transparently accesses that news host as if it was local.)
  16643. To achieve this, News uses a flood algorithm. Each member of the network only
  16644. communicates with a few neighbors (not all 40,000 members). When the member
  16645. site receives an article from one of its neighbors, it then sends it to all of
  16646. its other neighbors that have not already seen that article. This method
  16647. allows the article to be posted (created) at any individual site, and appear
  16648. at all sites in a very short time. Major backbone sites exchange news via
  16649. NNTP, the Network News Transfer Protocol, running on top of TCP/IP over the
  16650. Internet. This allows for very fast exchange of the messages and most backbone
  16651. sites have received a new posting within minutes of its entry onto the
  16652. network. Smaller sites usually use the UNIX UUCP protocol to exchange the
  16653. information, although there is no requirement that the UUCP protocol be used.
  16654. At present over 25MB of News are exchanged daily.
  16655. Twenty-five megabytes per day is too much for all but the major backbone sites
  16656. to handle, so most sites no longer get a "full feed." Instead, they only
  16657. receive a limited selection of the newsgroups. This brings us to the second
  16658. item, the organization of the information in News.
  16659. News articles are separated into divisions called newsgroups. Each division is
  16660. supposed to limit itself to a single topic, and the name of the group is
  16661. supposed to give you some idea as to the content of the group. These groups
  16662. are then organized into hierarchies of related topics. USENET Network News
  16663. started out with just two hierarchies, mod and net. The mod hierarchy had
  16664. those groups that had a person as the moderator to edit and control the
  16665. information. The net hierarchy handled all other groups. With the release of B
  16666. News and its ability to have any single group be moderated or open, the great
  16667. renaming was undertaken. So, for the last several years, News has consisted of
  16668. seven main or official hierarchies: comp (related to computers or computing),
  16669. misc (anything that didn't belong elsewhere), news (related to network news
  16670. itself, the software, distribution, groups or its administration), rec
  16671. (recreational topics), sci (scientific topics), soc (social groups), and talk
  16672. (discussion on controversial topics). In addition, there are several "private"
  16673. hierarchies including: bionet (biological science including the Human Genome
  16674. project), bit (bitnet sites and mailing lists, bitnet is another academic
  16675. network that is not the Internet), biz (business, a commercial hierarchy),
  16676. clari (paid subscription news including UPI style information), ddn (Defense
  16677. Data Network reports), gnu (reports from the Gnu's Not Unix project), ieee
  16678. (Institute for Electrical and Electronic Engineers), u3b (the AT&T 3B computer
  16679. line) and vmsnet (items of interest for DEC Vax/VMS users). There are also
  16680. unofficial hierarchies including the alt (alternate) hierarchy and a whole
  16681. host of regional hierarchies that specialize in regional news. At present
  16682. there are over 1800 different newsgroups to choose from.
  16683. This column normally reports on articles in the comp.sources and alt.sources
  16684. sub-hierarchies. These include: comp.sources.games, comp.sources.misc,
  16685. comp.sources.reviewed, comp.sources.unix, and alt.sources. Each of these is an
  16686. individual news group. All but alt.sources are moderated. For the moderated
  16687. groups, the authors of the software submit their packages to the moderator for
  16688. posting. Each group has its own rules for acceptance. Alt.sources is
  16689. unmoderated and a free-for-all. Sources in that group are posted directly by
  16690. the author.
  16691.  
  16692.  
  16693. What Happens Next?
  16694.  
  16695.  
  16696. With that much information flowing into each site every day, most sites cannot
  16697. keep the information on their local disks for very long. Usually only a couple
  16698. of days. So, by the time you ready my articles, the sources have been deleted
  16699. from the machines in the network to make room for newer articles. So, what
  16700. good does it do to report on what was available (or that is no longer
  16701. available)?
  16702. Most of the information posted on News is worth deleting in even less time
  16703. that it remains on the local disk. Source code, however, is not one of those
  16704. low value items. Many sites desire to keep it around so they and others can
  16705. make use of it. What those sites do is archive the packages so others can
  16706. access them. These sites are called, archive sites (surprise!). Since the
  16707. moderated source groups are purely source postings, with no other traffic, the
  16708. archiving task can be pretty automatic and many sites around the world have
  16709. agreed to do it.
  16710. Alt.sources is a different problem. Since it's a free-for-all, often items
  16711. other than source code end up in the group, and it takes more work to archive
  16712. it. Most sites do not archive this group. However, since the better postings
  16713. in alt.sources are previews of items to be posted later in one of the
  16714. mainstream moderated groups, there is hope that the source will be available
  16715. in those group archives.
  16716. The problem then, is finding out which sites archive which groups, and how to
  16717. access these archives. I would like to give credit to Jonathan I. Kames of the
  16718. Massachusetts Institute of Technology for gathering much of the data on how to
  16719. find the sources and posting it as a "Frequently Asked Questions" article to
  16720. the comp.sources.wanted newsgroup and the new moderated newsgroup
  16721. news.answers.
  16722.  
  16723.  
  16724. How To Find Sources
  16725.  
  16726.  
  16727. First you have to decide what it is you are trying to find. If it's one of the
  16728. source postings mentioned in my column, I always list the volume and Issue
  16729. numbers of the articles in the moderated groups, or just the posting date for
  16730. alt.sources. I also list a name in italics, this is the archive name of the
  16731. posting. It's a big help in finding the files once you find a site that
  16732. archives the information.
  16733.  
  16734.  
  16735. OK, Here Are The Steps:
  16736.  
  16737.  
  16738. 1. Figure out in what group, Volume, and Issue the posting appeared. Also try
  16739. and determine its archive name. If you know these items, it's usually easy to
  16740. find an archive site that keeps that group. Most archive sites keep their
  16741. information in a hierarchy ordered first on the group, then on the volume, and
  16742. last on the archive name. These together usually make up a directory path, as
  16743. in comp.sources.unix/volume22/elm2.3. In that directory you will find all of
  16744. the articles that made up the 2.3 release of the ELM Mail User Agent that was
  16745. posted in Volume 22 of the comp.sources.unix newsgroup. If you do not know the
  16746. archive name, but do know the volume, each volume also has an Index file that
  16747. can be retrieved and read to determine the archive name. One common publicly
  16748. accessible archive site for each of the moderated groups in this article is
  16749. UUNET.
  16750. 2. If you do not know which sites archive the groups, or if any site is
  16751. archiving that item, even though they are not archiving the entire group,
  16752. consult Archie. (CUJ August 1991, Vol. 9, No. 8). Archie is a mail response
  16753. program that tries to keep track of sites reachable via FTP (file transfer
  16754. protocol, a TCP/IP protocol used by internet sites) that have sources
  16755. available for distribution. Even if you cannot access the archive site
  16756. directly via FTP, it is worth knowing that the archive site exists because
  16757. there are other ways of retrieving sources only available via FTP.
  16758. 3. If you know the name of the program, but do no know what group it was
  16759. posted in, try using Archie and search based on the name. Since most sites
  16760. store the archives by group and volume, the information returned will tell you
  16761. what newsgroup and volume it was posted in. Then you can retrieve the item
  16762. from any archive site for that newsgroup.
  16763. 4. If you do not know the name, but know you are looking for source code that
  16764. performs a particular task, retrieve the indexes for each of the newsgroups
  16765. and see if any of the entries (usually listed as the archive name and a short
  16766. description of the function) look reasonable. If so, try those. Or, make a
  16767. query to Archie based on some keywords from the function of the software and
  16768. perhaps it can find items that match. If the task is a mathematical item or
  16769. algorithmic program that is commonly solved by computer, checkout the netlib
  16770. archive at AT&T (mentioned later in this column) available via both FTP or
  16771. electronic mail.
  16772.  
  16773.  
  16774. How To Transfer The File
  16775.  
  16776.  
  16777. Ok, you have now found the machine that has the software you need, how do you
  16778. actually get it back to your machine?
  16779. First you have to determine what access methods the archive machine allows to
  16780. retrieve software. Most archive sites are internet based, and support the FTP
  16781. service. If you have access to FTP on the internet, this is the easiest, and
  16782. fastest way of retrieving the sources. If you don't, perhaps a local college
  16783. is a member of the internet and can assist you in using FTP to retrieve the
  16784. sources you need.
  16785. Other sites support anonymous UUCP access. This is access via the UUCP
  16786. protocol where you don't need to register in advance to call in. UUNET
  16787. Communications Services supplies this using the (900) GOT-SRCS number at
  16788. $.40/minute for non subscribers. Many other archive sites provide it for just
  16789. the cost of your long distance telephone call. If you cannot use FTP, this is
  16790. the next best method to use.
  16791. Many anonymous UUCP archive sites list what they carry in the Nixpub listing
  16792. of public access Unix sites maintained by Phil Eschallier. The Nixpub listing
  16793. is posted in comp.misc and alt.bbs periodically. If you don't get News and
  16794. need a copy, it can be retrieved via electronic mail using the "periodic
  16795. posting mail based archive server." This is run by MIT on the system
  16796. pit-manager.mit.edu. To use the server, send an electronic mail message to the
  16797. address mail-server@pit-manager.mit.edu with the subject "help" and it will
  16798. reply with instructions on how to use the server.
  16799. If you are not on USENET, sending electronic mail to sites on the internet is
  16800. also possible via the commercial mail services. CompuServe, MCI-Mail, ATT-Mail
  16801. and Sprint-Mail all support sending messages to Internet addresses. Contact
  16802. the support personnel at the commercial mail service you use for details on
  16803. how to send messages to Internet addresses.
  16804. The last way to access the sources is via electronic mail. Several sites also
  16805. make their archives available via automated mail-response servers. Note, this
  16806. can be a very expensive way of accessing the information, and due to the load
  16807. it places on the networks, most archive servers heavily restrict the amount of
  16808. information they will send each day. This can lead to long waits for access to
  16809. the source you are trying to retrieve. The following are some of the sites
  16810. that maintain mail based archives:
  16811.  
  16812. hrc!archives: This site requires that you issue two commands to get the help
  16813. message. Place these lines in the body of your electronic mail message:
  16814. send path <address>
  16815. send help
  16816. The <address> should be a UUCP path from a well known site to your mailbox.
  16817. netlib@uunet.uu.net: E-mail access is provided to most of the sources archived
  16818. by UUNET. Place the word "help" in the body of your message to receive
  16819. information on using the server.
  16820. ftpmail@decwrl.dec.com: Digital Equipment Corporation runs a mail based
  16821. archive server that will retrieve sources via FTP and then mail them to you.
  16822. To find out how to use this service, send it a message with the word "help" in
  16823. the body.
  16824. netlib@research.att.com: All of the algorithmic and mathematical software
  16825. available via ftp in the netlib archives at AT&T is also available via
  16826. electronic mail. Again, send a message with the word "help" in the body for
  16827. further instructions on using the service.
  16828. There are also other servers that specialize in particular sources. Send a
  16829. message to these with the word "help" in the body for further information.
  16830. Selected entries from Jonathan's posting in comp.sources,wanted are:
  16831. archive-server@ames.arc.nasa.gov: Space archives (also accessible via
  16832. anonymous ftp to ames.arc.nasa.gov)
  16833. archive-server@athena-dist.mit.edu: MIT Project Athena papers and source code
  16834. (also accessible via anonymous ftp to athena-dist.mit.edu)
  16835. archive-server@bcm.tmc.edu: UUCP maps, source-code for BCM WHOIS database, NFS
  16836. and PC-NFS information and source-code, Unisys U-series information and source
  16837. code, other stuff
  16838. archive-server@cc.purdue.edu: NeXT stuff (also accessible via anonymous ftp to
  16839. sonta.cc.purdue.edu or nova.cc.purdue.edu)
  16840. archive-server@chsun1.uchicago.edu: Computer Underground Digest and references
  16841. archive-server@cs.leidenuniv.nl: IPX, patch for MS-DOS, sps diffs for SunOS
  16842. 4.1
  16843. archive-server@dsi.com: elm, patch
  16844. archive-server@ecletic.com: Mac-security digest, information about Eclectic,
  16845. other stuff
  16846. archive-server@ncsa.uiuc.edu: NCSA stuff, especially telnet and tcp for MAC
  16847. and PC compatibles.
  16848. archive-server@rice.edu: Sun-spots, sun-source and sunicons, plus other
  16849. software written or influenced by people at Rice (also accessible via
  16850. anonymous ftp to titan.rice.edu)
  16851. archive-server@sun.soe.clarkson.edu: IBM and other good stuff (also accessible
  16852. via anonymous ftp to sun.soe.clarkson.edu)
  16853. info-server@cl.cam.ac.uk: Various random stuff, including bmx, btoa, c-nrs,
  16854. gdb, soft-gen, spad, top, unix-niftp, ups (Unix PostScript interpreter)
  16855. info-server@doc.ic.ac.uk: USENET source newsgroups, GNU, X11, news software,
  16856. other stuff
  16857. info-server@hp4nl.nluug.nl: Macintosh, Sun, IBM-PC, Unix sources, some
  16858. documents, GNU, graphics, USENET archives (or lots of newsgroups), X window
  16859. system, TeX, programming languages (LISP, ICON, ABC, others), news sources,
  16860. network sources, other stuff
  16861. mail-server@cs.ruu.nl: GIFs, Atari ST software, random documentation, ELM
  16862. sources, USENET FAQ postings, GNU software HP-UX software, NN sources, SGI
  16863. software, TeX software and TeXhax and TeXmag archives, random UNIX software,
  16864. X11 software, other stuff (also accessible via anonymous ftp to
  16865. praxis.cs.ruu.nl
  16866. mail-server@rusmv1.rus.uni-stuttgart.de: German TeX archives; benchmarks,
  16867. journal indices, RFCs, network info, UNIX info; X, mac, pc, sun, aix, vax, and
  16868. other software (also accessible via anonymous ftp to
  16869. rusmv1.rus.uni-stuttgart.de)
  16870. mailserv@garbo.uwasa.fi: Frequently asked questions in various areas, some
  16871. USENET source archives, some PC software archives
  16872. netlib@draci.cs.uow.edu.au: Australian Netlib (also accessible via anonymous
  16873. ftp to draci.cs.uow.edu.au)
  16874. netlib@ornl.gov: Similar to the AT&T netlib archive
  16875. netlib@ukc.ac.uk: UK netlib server (mostly same contents as AT&T's netlib)
  16876. (some files also accessible via anonymous ftp to harrier.ukc.ac.uk {username
  16877. guest})
  16878.  
  16879.  
  16880. Very Important
  16881.  
  16882.  
  16883. It is considered very poor form to use a mail based archive server if any
  16884. other method is available to you. In addition, accessing any service that is
  16885. on a different continent than your own via electronic mail is also frowned
  16886. upon. It is very expensive to transmit information across the oceans. If you
  16887. use electronic mail via one of these archive-servers, you are forcing someone
  16888. else to pay those charges. Abuse of this will cause the sites to remove their
  16889. archive servers, which will hurt everyone. So, please, those in the United
  16890. States and Canada, please restrict your accesses to those servers in the US
  16891. and Canada, and those in Europe to those in Europe, and those in Australia, to
  16892. those in Oz.
  16893. But how do I tell where the site is located? Internet addresses do give you a
  16894. clue. You can tell the country an Internet named site (those with @ in their
  16895. addresses) is located in by the abbreviation at the end of the address string.
  16896. Thus .ca is Canada, .ge is Germany, and .au is Australia. Please don't spoil
  16897. access for everyone else (and your own future access) by tieing up the
  16898. transoceanic links with mail based archive server traffic.
  16899. Hopefully, this special edition of my column has given you a hint as to how to
  16900. track down the sources. Note, I have been asked many times if I can make
  16901. floppies or tapes containing the software mentioned in my column. I cannot
  16902. spare the time to do this. I also have to work for a living, and if I started
  16903. doing this, I could easily spend all my time trying to fulfill the requests
  16904. and never get any of my work done.
  16905.  
  16906.  
  16907.  
  16908.  
  16909.  
  16910.  
  16911.  
  16912.  
  16913.  
  16914.  
  16915.  
  16916.  
  16917.  
  16918.  
  16919.  
  16920.  
  16921.  
  16922.  
  16923.  
  16924.  
  16925.  
  16926.  
  16927.  
  16928.  
  16929.  
  16930.  
  16931.  
  16932. Editor's Forum
  16933. Well, it's a new year. I am somewhere in my second year of editing The C Users
  16934. Journal. R&D Publications has moved to new quarters, which I have yet to
  16935. visit. And the C programming language is drifting into its third decade of
  16936. existence.
  16937. I must say that I have mostly enjoyed editing this magazine. Working via
  16938. e-mail from Australia was often a challenge. Some issues I feel I could have
  16939. polished better with a bit more effort. But an occasional kind word from a
  16940. reader goes a long way in this business. The hardest part for me is reading
  16941. the other kind of letters. I find that a little criticism also goes a long
  16942. way. The trick is not to overreact, in either direction.
  16943. R&D seems to be prospering as well, at least from my vantage point as a
  16944. semi-insider. Besides The C Users Journal, they also put out several other
  16945. quality technical publications. (These are not as important as CUJ, of course,
  16946. but you might want to check them out anyway.) And they run The C Users' Group
  16947. and The C Users Bookstore, two valuable services. I expect all of these
  16948. endeavors to become more important as the C community grows.
  16949. In the early 1970s, that community was confined to two floors of one building
  16950. at Bell Labs, Murray Hill, New Jersey. To say that it has grown is the
  16951. grossest of understatements. C is ubiquitous. It is probably the programming
  16952. language in widest use today. We all know that C is not perfect. That's one
  16953. reason why people keep tinkering with it and extending it. But the list of
  16954. successful products written in C keeps growing.
  16955. Not all my reflections on past and future are equally rosy. Certainly, the
  16956. world is wallowing through times of economic uncertainty. Not even the rapidly
  16957. growing computer business is immune to setbacks. And economic hard times have
  16958. a way of depressing everything else.
  16959. Still, it's a new year. I can't help but look on the coming months with
  16960. optimism, at least for CUJ, R&D, and C. I hope you can too.
  16961. P.J. Plauger
  16962. pjp@plauger.uunet
  16963.  
  16964.  
  16965.  
  16966.  
  16967.  
  16968.  
  16969.  
  16970.  
  16971.  
  16972.  
  16973.  
  16974.  
  16975.  
  16976.  
  16977.  
  16978.  
  16979.  
  16980.  
  16981.  
  16982.  
  16983.  
  16984.  
  16985.  
  16986.  
  16987.  
  16988.  
  16989.  
  16990.  
  16991.  
  16992.  
  16993.  
  16994.  
  16995.  
  16996.  
  16997.  
  16998.  
  16999.  
  17000.  
  17001.  
  17002.  
  17003.  
  17004.  
  17005.  
  17006.  
  17007.  
  17008.  
  17009.  
  17010.  
  17011.  
  17012.  
  17013.  
  17014.  
  17015. New Products
  17016.  
  17017.  
  17018. Industry-Related News & Announcements
  17019.  
  17020.  
  17021.  
  17022.  
  17023. Supercomputer For The 90s
  17024.  
  17025.  
  17026. Cray Research, Inc. has introduced a parallel vector computer system, the CRAY
  17027. Y-MP C90 supercomputer. The system has 16 central processing units (CPUs), and
  17028. on actual customer problems, operates four times the speed of Cray Research's
  17029. previous fastest supercomputers.
  17030. The CRAY Y-MP C90 system features a CPU with a peak performance of one billion
  17031. floating-point operations per second (Gflop/s). With 16 of these powerful CPUs
  17032. and 256 megawords (2 gigabytes) of central memory, the CRAY Y-MP C90 system
  17033. has a peak performance of 16 Gflop/s.
  17034. The CRAY Y-MP C90 system uses a balanced parallel, vector-scalar architecture
  17035. to maximize sustained performance. A significant feature of the CRAY Y-MP C90
  17036. system is a dual-vector pipeline that allows each of the system's 16 CPUs to
  17037. deliver two vector results per functional unit every clock period. With its
  17038. 64-way parallelism and efficient multiprocessing capabilities, the CRAY Y-MP
  17039. C90 system can deliver a total of 64 vector results per clock period, or four
  17040. times that of Cray Research's previous top-end systems.
  17041. For more information contact Cray Research, Inc., 655A Lone Oak Dr., Eagan, MN
  17042. 55121, (612) 683-7100.
  17043.  
  17044.  
  17045. New Generation Of Anti-virus Software
  17046.  
  17047.  
  17048. Fifth Generation Systems, Inc. has released Untouchable, a line of anti-virus
  17049. software that provides virus protection without the need for frequent virus
  17050. signature updates. Untouchable stand-alone and network products are designed
  17051. to operate on DOS-based PCs, compatibles, and networks.
  17052. Untouchable consists of three distinct anti-virus technologies that constantly
  17053. interact to detect and eradicate known and unknown viruses. The file scanner
  17054. incorporates the principal of modification detection. This
  17055. mathematically-proven technique safeguards against known and unknown virus
  17056. threats by making use of file signatures rather than the commonly-used virus
  17057. signatures. A TSR monitor checks system memory for known boot sector and
  17058. partition table viruses.
  17059. In addition, a virus scanner/remover can detect and eradicate hundreds of
  17060. known viruses.
  17061.  
  17062.  
  17063. Untouchable Network
  17064.  
  17065.  
  17066. Untouchable Network provides Novell LAN administrators with the ability to
  17067. centrally monitor and control the entire network for viruses
  17068. The program uses the same methods of protection as the stand-alone version,
  17069. but incorporates enhanced LAN management features. Untouchable Network
  17070. currently provides its enhanced LAN management facilities for the Novell
  17071. Netware operating system. A network savvy version of the product, without the
  17072. LAN management features, is available to run on Microsoft LAN Manager, Banyan
  17073. Vines, 3Com, and other PC network operating systems.
  17074. Untouchable is available for $165. An Untouchable Network Starter Kit is
  17075. available for $695. The starter kit includes the server package of full
  17076. supervisor documentation and software and ten node license agreements. License
  17077. agreements for additional nodes may be purchased for $85 per node.
  17078. While Untouchable does not require virus signature updates to ensure complete
  17079. protection against undiscovered new viruses, Fifth Generation Systems
  17080. recognizes that some users may want them. The company will provide quarterly
  17081. virus signature updates for the virus scanner and TSR monitor, for a service
  17082. charge of $15 per quarter.
  17083. For more information contact Fifth Generation Systems, 10049 N. Reiger Rd.,
  17084. Baton Rouge, LA 70809, (504) 291-7221.
  17085.  
  17086.  
  17087. ROMable Operating System
  17088.  
  17089.  
  17090. Datalight has released a new version of ROM-DOS, the small, low-cost,
  17091. modifiable operating system that can run from within ROM or on a floppy or
  17092. hard disk. The new version of ROM-DOS is compatible with MS-DOS v3.31 and has
  17093. many added features such as support for Datalight FLASH Memory Disk,
  17094. Installable File Systems, configurable memory disks and hard disks up to 512
  17095. Megabytes.
  17096. ROM-DOS compatibility with common desktop DOS has been certified and
  17097. documented by VeriTest, an independent testing agency. VeriTest successfully
  17098. ran a collection of 28 widely used MS-DOS applications under ROM-DOS,
  17099. including: AutoCAD, dBASE IV, Freelance Plus, Lotus 1-2-3, Microsoft Word,
  17100. Microsoft Works, Multimate, Paradox, R:BASE, Sidekick, Turbo Pascal, Ventura
  17101. Publisher, and Word Perfect.
  17102. ROM-DOS allows a developer to write the application program in the desired
  17103. language, then convert the program into a ROMable EXE (named RXE) that will
  17104. execute the code directly from ROM. The RXE is loaded into ROM along with
  17105. ROM-DOS to run in the target system with no further modifications required.
  17106. Developers can place the memory intensive program code in ROM and use valuable
  17107. RAM space only for the remaining program data.
  17108. ROM-DOS resides in about 34K ROM, and uses as little as 10K RAM when running.
  17109. ROM-DOS can be further minimized when customized to include only specifically
  17110. needed functions. ROM-DOS can also be run with Datalight's mini-BIOS which
  17111. requires less than 3K of ROM. The ROM-DOS Developer's Kit contains the tools
  17112. and utilities needed to port ROM-DOS to the developer's choice of hardware
  17113. platform. Also included are numerous DOS utilities, source code for all device
  17114. drivers, and source code for the mini-BIOS.
  17115. ROM-DOS can be purchased in quantity for as low as $5 per copy. The
  17116. Developer's Kit is available for $495. License to the source code is available
  17117. for $10,000. For more information contact Datalight, 17455 - 68th Ave NE,
  17118. Suite 304, Bothell, WA 98011, 1-800-221-6630.
  17119.  
  17120.  
  17121. UNIX SVR4 For Independent Software Vendors (ISVs)
  17122.  
  17123.  
  17124. Motorola Inc. has released its enhanced UNIX System V Release 4, v3
  17125. (SVR4)-compatible operating system. UNIX SYSTEM V/88 Release 4.0, v3.1
  17126. supports Motorola's MC88000 based reduced instruction set computer (RISC)
  17127. systems architecture and complies with UNIX Systems Laboratory's Application
  17128. Binary Interface (ABI); X/Open's Portability Guide, Issue 3 (XPG) Issue 3)
  17129. from X/Open; and Binary Compatibility Standard (BCS) and Object Compatibility
  17130. Standard (OCS) criteria established by the 88open Consortium. This
  17131. compatibility provides seamless migration from Motorola SYSTEM V/88 Release
  17132. 3.2 v2 and v3.
  17133. UNIX SYSTEM V/88 Release 4.0, v3.1 includes the BOS, NSE and DeltaWINDOWS 1.2.
  17134. The early access release is now shipping to ISVs for development of
  17135. off-the-shelf software applications and other key customers of the MCG.
  17136. For more information contact Motorola, Inc. Computer Group, 2900 South Diablo
  17137. Way, Tempe, AZ 85282, (602) 438-3576.
  17138.  
  17139.  
  17140. Improved Laser Printer Output And Mouse Event-driven GUI's
  17141.  
  17142.  
  17143. Clarion Software is now shipping v4. 1 of its Graphics Language Extension
  17144. Module (LEM) for the Clarion Professional Developer. Version 4.1 improves that
  17145. quality of output to laser printers and allows users to create mouse
  17146. event-driven graphical user interfaces (GUIs).
  17147.  
  17148. Version 4.1 of the Graphics LEM provides functions that allow users to print
  17149. full-page, 300 dpi vector output to Hewlett-Packard LaserJet Series III and
  17150. PostScript printers, eliminating jagged lines and enhancing graphics
  17151. resolution. Version 4.1 also is better equipped to handle .PCX images. An
  17152. updated function correctly sets the color palette from the header of the .PCX
  17153. file and allows the display of .PCX images larger than the screen. New
  17154. functions also now allow users to remap the standard Graphics LEM 16 colors to
  17155. any color supported by the EGA or VGA card in use. For instance, to display a
  17156. .PCX image that has eight shades of blue, users can remap 8 of the 16 colors
  17157. to the various shades of blue. Though users may continue to display only a
  17158. maximum of 16 colors, these colors can range outside the standard Graphics LEM
  17159. color palette.
  17160. The simplify creation of GUIs, 4.1 of the Graphics LEM can save and restore
  17161. partial graphics screens to create popdown, tile, icon, and many other screen
  17162. styles, all using only the Graphics LEM's capabilities. Another new function
  17163. identifies a rectangular area of the screen as an active clipping region.
  17164. Parts of objects extending outside the designated area are not drawn. New
  17165. vector printing functions allow users to print only the "clipped" area of the
  17166. screen.
  17167. Version 4.1 also includes a number of new and improved utilities and fonts.
  17168. Utilities allow users to debug .PCX-based image-handling applications, improve
  17169. the look of applications, speed application design, and allow for more
  17170. flexible handling of custom created or edited fonts.
  17171. Enhancements to the GFONT icon and font editor allow users to map fonts to
  17172. equivalent fonts native on PostScript or Hewlett-Packard LaserJet Series III
  17173. printers. They also provide improved scaling capabilities. The ability to
  17174. import a .PCX file to create new icons or fonts also has been improved.
  17175. Included in the new graphics functions is a 3-D "caged" bar-chart function
  17176. that creates a 3-D rendering of a "caged" background. The ability to display a
  17177. time-series graph also has been improved. For first-time buyers, v4.1 of the
  17178. Graphics LEM is priced at $199. Current registered users of the Graphics LEM
  17179. can upgrade for $50. For registered users who purchased the product on July 1,
  17180. 1991, or later, the upgrade is free.
  17181. For more information contact Clarion Software, 150 East Sample Road, Pompano
  17182. Beach, Fl 33064, (800) 354-5444.
  17183.  
  17184.  
  17185. Picture and Document Imaging Library
  17186.  
  17187.  
  17188. Videotex Systems has released v3 of T-BASE, its advanced picture and document
  17189. imaging library. Bundled with T-BASE is Chroma Tools, an advanced color
  17190. manipulation and imaging conversion utility. ChromaTools is also form Videotex
  17191. Systems, Inc. T-BASE allows developers to add pictures and document images to
  17192. their database management applications written in C, C++, and virtually every
  17193. Xbase dialect. T-BASE supports any image in the PCX file format.
  17194. T-BASE is also hardware independen, so it does not limit you to certain types
  17195. of video cards or other equipment T-BASE automatically detects your hardware
  17196. configuration and adjusts itself to work in that configuration. ChromaTools,
  17197. which is bundled with T-BASE, is an advanced color manipulation and image
  17198. conversion utility that makes it easy to convert images in a varieyt of file
  17199. formats into a single format for inclusion in desktop presentations, CD-ROM
  17200. libraries, picture databases, demo disks, bulletin boards, advertising,
  17201. kiosks, and more. ChromaTools is a $249 value.
  17202. T-BASE has a suggested retail price of $495 and is available from dealers
  17203. nationwide as well as from Videotex Systems, Inc. For more information contact
  17204. Videotex Systems, Inc., 8499 Greenville Ave, Suite 205, Dallas, TX 75231,
  17205. (800) 888-4336, Fax (214) 348-3821
  17206.  
  17207.  
  17208. Upgrade To Sourcerer's Apprentice
  17209.  
  17210.  
  17211. Solution Systems has released v2.0 of its Sourcerer's Apprentice configuration
  17212. management system. Sourcerer's Apprentice enables developers and managers to
  17213. organize a project according to file extension -- by source, by object, by
  17214. headers -- or however they choose, allowing them to get at selected modules
  17215. immediately and intuitively.
  17216. Sourcerer' s Apprentice professional version carries security, compression,
  17217. archiving, and additional reporting features Project leaders or managers can
  17218. secure Sourcerer's Apprentice from unauthorized access by assigning
  17219. permissions based on the user I.D. and module name. Archiving allows users to
  17220. save versions to an external medium, such as a floppy disk, tape or server.
  17221. For programmers working off a Novell server, Sourcerer's Apprentice offers
  17222. automatic file server support.
  17223. Sourcerer's Apprentice requires IBM 286, 386, or compatibles. DOS version
  17224. requires DOS 2.0 or higher; OS/2 requires OS/2 v1.2 or higher; Windows
  17225. requires Microsoft Windows 3.0 or higher. For more information contact
  17226. Solution Systems, 372 Washington St, Wellesley, MA 02181, (800) 677-0001,
  17227. (617) 431-2313, Fax (617) 740-0089.
  17228.  
  17229.  
  17230. Gimpel Software Releases PC-Lint 5.0
  17231.  
  17232.  
  17233. Gimpel Software has released v5.0 of PC-lint, a source code analysis tool for
  17234. the C programming language. PC-lint analyzes C programs and reports on bugs,
  17235. glitches, and inconsistencies. Two important new features of PC-lint v5.0 are,
  17236. a Strong Type checking facility and a control-flow based analysis of variable
  17237. initialization. The Strong Typing facility is based on typedef types. In
  17238. addition, Boolean operations can be restricted to a Boolean type, and
  17239. subscript types can be validated. A natural type hierarchy is supported and
  17240. can be supplemented by the user. The control-flow based analysis will report
  17241. on possibly uninitialized variables. The analysis takes into account all C
  17242. control structures as well as the properties of system functions such as free
  17243. and exit and user-specified equivalents. Other constructs that are more
  17244. closely scrutinized in v5.0 are constant expressions, initializers, and
  17245. expressions involving overflow or lost information.
  17246. For more information contact Gimpel Software, 3207 Hogarth Lane, Collegeville,
  17247. PA 19426, (215) 584-4261.
  17248.  
  17249.  
  17250. GIF Image Support Announced For Windows Applications
  17251.  
  17252.  
  17253. Black Ice Software, Inc. has released the Graphics Interchange Format (GIF)
  17254. Software Development Kit (SDK) for Windows. The GIF graphics format was
  17255. designed several years ago to allow the exchange of high-resolution color
  17256. images between different hardware platforms. Introduced into the public domain
  17257. via the CompuServe network, tens of thousands of GIF images are now available
  17258. on bulletin boards and information network systems across the country. The GIF
  17259. SDK is designed to satisfy the growing commercial demand for a simple means of
  17260. adding support for this versatile graphics format within Windows-based
  17261. applications.
  17262. The routines included in the GIF SDK make it easy to load GIF files into
  17263. Device Independent Bitmaps (DIBs) or save a bitmap in the GIF format with a
  17264. single function call. Packaged in the form of a DLL (Dynamic Link Library),
  17265. the GIF DSK is compatible with a variety of development environments, such as
  17266. SQL Windows, Actor, Borland's C++, Microsoft's Visual Basic, and others.
  17267. The GIF SDK is priced at $149, and includes complete documentation for use in
  17268. application development. For more information contact Black Ice Software,
  17269. Crane Rd., Somers, NY 10589, (914) 277-7006, Fax (914) 276-8418.
  17270.  
  17271.  
  17272. Inductive Logic
  17273.  
  17274.  
  17275. Inductive Logic has released v1.3 of InCommand, productivity enhancement
  17276. utilities for DOS. Version 1.2 was chosen a Publisher's Pick by PCM Magazine
  17277. in October, 1991. New in v1.3 is the ability to match, or exclude, several
  17278. wildcard patterns in one command, and a complete on-line DOS command
  17279. reference, as well as several other enhancements to existing functions.
  17280. InCommand is intended for DOS command line users at all levels. It provides
  17281. commands to list directories (including locating lost files), copy files, move
  17282. files and directories without copying, delete files, directories, and entire
  17283. trees, change file attributes, set file dates and times, locate external DOS
  17284. commands, search files for a text string, and more.
  17285. The InCommand utilities are designed for both interactive and batch use, and
  17286. provide a complete set of options for omitting confirmation prompts and file
  17287. lists. All utilities return consistent exit codes, for full automation in
  17288. batch files. System requirements are an IBM compatible PC, DOS 3.0 or higher,
  17289. and 256K RAM. A hard disk is strongly recommended. For more information
  17290. contact Inductive Logic, P.O. Box 26238, San Diego, CA 92196-0238, (619)
  17291. 578-5146.
  17292.  
  17293.  
  17294. Liant Software Announces X Window Support For "C-Space 3.2C UNIX"
  17295.  
  17296.  
  17297. With C-scape and "Look & Feel," developers can create sophisticated
  17298. applications for X
  17299. Liant Software Corp today announced X Window support for C-scape, the
  17300. company's comprehensive library of C routines for creating professional user
  17301. interfaces. C-scape 3.2C UNIX enables C-scape developers to write
  17302. sophisticated user interfaces for graphical applications under the X Window
  17303. System. Existing C-scape applications can also be recompiled under X. C-scape
  17304. applications offer programmers a variety of graphical features, including menu
  17305. systems, borders and pop-up windows. Text editing functions include word wrap,
  17306. search and replace, and block commands. Mouse support allows users to
  17307. manipulate windows or select menu choices.
  17308. These features can be incorporated into screens using Look & Feel, Liant's
  17309. screen designer which comes with C- scape. C-scape 3.2C UNIX has a suggested
  17310. retail price in the U.S. of $1499. Versions of C-scape for VMS, MS-DOSX, OS/2
  17311. and QNX are also available. Each version of C-scape comes with a set of
  17312. example programs and source code which users can modify according to their
  17313. needs.
  17314. For more information contact Liant Software Corp, (508) 626-0006.
  17315.  
  17316.  
  17317. C++ Version Of Linpack Announced
  17318.  
  17319.  
  17320. Rogue Wave Software, Inc. announces today that it has started shipping two new
  17321. C++ class libraries, Matrix.h++ and Linpack.h++. These new C++ class libraries
  17322. extend the C++ language to include numerical algorithms that were previously
  17323. available only in FORTRAN.
  17324.  
  17325. Linpack.h++ is an object-oriented C++ version of the widely used FORTRAN
  17326. library. Linpack.h++ includes 100% of the functionality of the FORTRAN
  17327. version, plus much more. Because Linpack.h++ is written in C++ it has
  17328. capabilities that far exceed the FORTRAN version.
  17329. Rogue Wave's new libraries take full advantage of C++'s strengths: operator
  17330. overloading, object-orientation, and speed. Programmer productivity is boosted
  17331. because you work with whole objects that represent numerical data rather than
  17332. low-level "DO" loops. The result is fewer, but more expressive, lines of code.
  17333. Both Matrix.h++ and Linpack.h++ are completely compatible with Rogue Wave's
  17334. other C++ class libraries, Tools.h++ and Math.h++. Matrix.h++ and Linpack.h++
  17335. are available for most machines, from MS-DOS to UNIX, including a vectorized
  17336. version for the CRAY. Matrix.h++ includes all the functionality of Math.h++.
  17337. Matrix.h++ adds specialized matrix classes such as banded, symmetric,
  17338. positive-definite, Hermitian, tridiagonal, etc. Because Matrix.h++ includes
  17339. Math.h++, it can take advantage of Math.h++'s optimized lowlevel assembly
  17340. routines.
  17341. Linpack.h++ includes all of Matrix.h++, plus all of the functionality in the
  17342. original and well-established FORTRAN version; including solutions of systems
  17343. of equations for a variety of matrix types, solutions of over- and
  17344. under-determined systems or equations, incremental least squares solvers, etc.
  17345. But, Linpack.h++ is a true object-oriented library, not just a C version that
  17346. compiles under C++: the FORTRAN version has been replaced with high-level
  17347. objects.
  17348. The classes are available now. Prices range from $199 to $995 for Matrix.h++
  17349. and $299 to $1195 for Linpack.h++. For more information contact Rogue Wave
  17350. Software Inc., 1325 NW 9th Street, Corvallis, OR 97330, (503) 757-2311.
  17351.  
  17352.  
  17353.  
  17354.  
  17355.  
  17356.  
  17357.  
  17358.  
  17359.  
  17360.  
  17361.  
  17362.  
  17363.  
  17364.  
  17365.  
  17366.  
  17367.  
  17368.  
  17369.  
  17370.  
  17371.  
  17372.  
  17373.  
  17374.  
  17375.  
  17376.  
  17377.  
  17378.  
  17379.  
  17380.  
  17381.  
  17382.  
  17383.  
  17384.  
  17385.  
  17386.  
  17387.  
  17388.  
  17389.  
  17390.  
  17391.  
  17392.  
  17393.  
  17394.  
  17395.  
  17396.  
  17397.  
  17398.  
  17399.  
  17400.  
  17401.  
  17402.  
  17403.  
  17404.  
  17405.  
  17406.  
  17407.  
  17408.  
  17409.  
  17410. We Have Mail
  17411. Dear Sir:
  17412. I am a computer science instructor for one of the numerous universities that
  17413. use Pascal as the primary teaching language. Recently, at a curriculum
  17414. planning group meeting, I suggested that we give serious consideration to
  17415. adopting C as our primary language. For a brief time I replaced the USSR as
  17416. America's principal adversary. All sorts of reasons, including difficulty and
  17417. treachery, were paraded as reasons why we shouldn't even consider such change.
  17418. And yet I persist, for I feel that at graduation CSS students should take
  17419. useful knowledge into the world.
  17420. Now, the question: do you good folk at The C Users Journal know of any schools
  17421. that currently use C or contemplate using C as their primary language? If
  17422. there are any, I need to review their experience and particular problems, so
  17423. that I can make a better case for my own agenda. Please tell me what you know.
  17424. I will appreciate it.
  17425. Sincerely,
  17426. Casy Ver Berkmoes
  17427. Coordinator, Undergraduate Computer
  17428. Science Department
  17429. The University of Southern Mississippi
  17430. Long Beach, MS 39560
  17431. I suspect P.J. Plauger might answer this differently, but since I'm here and
  17432. he's in Europe, and I've taught quite a bit of introductory programming, I'll
  17433. slip in my two cent's worth.
  17434. I don't recommend using C as a primary teaching language, for several reasons.
  17435. -C is not a "safe" language. The lack of run-time type and bounds checking
  17436. allows the student to commit errors that can't be found without fairly
  17437. advanced understanding of the run-time environment. It's not reasonable to
  17438. make an understanding of compiler code generation, operating system linkages,
  17439. assembly language debugging tools, and other advanced topics be a prerequisite
  17440. to effective debugging. Not only will students fail to find the bug, the
  17441. seeming randomness of certain C bugs will encourage students to trust to
  17442. uncontrolled trial and error debugging and foster a belief in "black magic"
  17443. explanations.
  17444. -Appreciation of some of C's most important strengths requires extensive
  17445. knowledge of the environment. Separate compilation, direct manipulation of the
  17446. hardware, ease of assembly language integration, bit-wise operators,
  17447. pointer/array equivalences -- these are some of C's greatest strengths, but
  17448. are not even remotely related to the kinds of problems with which a beginning
  17449. student should be struggling.
  17450. -Many important effects are achieved through non-obvious means. For example,
  17451. by putting subsystems into separate files, creating the right kinds of
  17452. headers, and labelling certain functions static, the programmer can create an
  17453. encapsulated type. The indirectness of the implementation only complicates the
  17454. teaching of the core concept -- a concept that is fairly difficult to
  17455. communicate anyway.
  17456. -The elegance of the language can't be appreciated until you've tried to solve
  17457. fairly demanding problems in other languages. A student who is asked to do
  17458. classroom exercises in C will probably invest a lot of energy asking "why
  17459. can't I just have a string type". On the other hand, the student who has tried
  17460. to pass a Pascal string to a "Pascal-hostile" operating system will just be
  17461. thankful he has the tool. It's unwise to push the advanced tool onto the
  17462. student before they've experienced at least some of the situations that
  17463. created the need. Otherwise, the student isn't developmentally prepared to
  17464. internalize the nuance of using the tool.
  17465. Having said all this, I must admit that I have talked with faculty who use C
  17466. as the primary teaching language. Unfortunately, I don't recall any names. If
  17467. they are reading, I hope they'll contact you and share their experiences. --
  17468. rlw
  17469. Dear Mr. Plauger:
  17470. With regard to the article "Doing Fractions in C++" in the November 1991
  17471. issue, I found the topic to be interesting and relevant, and commend Mr.
  17472. Zeidler for doing the programming and writing. Unfortunately, Mr. Zeidler did
  17473. this work while learning C++, and made a serious mistake in his design. I am
  17474. afraid that his mistake may be duplicated by other C++ newcomers who read the
  17475. article; hence this letter to point out the problem and propose an
  17476. alternative.
  17477. The problem I am referring to is the class structure that has the fraction
  17478. class inherit the gcd_cls class.
  17479. Actually, the gcd_cls should not have been used at all. A gcd is simply a
  17480. number (integer), and the gcd operation is performed on two integers. In C++,
  17481. the compiler handles integers (or longs in the case of these fractions) as an
  17482. elementary data type. The gcd operation should be introduced simply as a
  17483. function with two unsigned long arguments that returns an unsigned long, thus
  17484. extending the set of functions available to operate on the built-in unsigned
  17485. long data type, as in
  17486. unsigned long gcd ( unsigned long,
  17487. unsigned long );
  17488. A fraction is not a special case of a gcd. There is a relationship between
  17489. fractions and gcds, but only in the sense that the gcd operation (function) is
  17490. needed to help keep fractions in simplest terms.
  17491. Why all the fuss, if the approach used by Mr. Zeidler "works?" Well, it works,
  17492. but incorrectly. Each fraction instance contains the numerator, the
  17493. denominator, plus an instance of gcd_cls. An instance of gcd_cls contains
  17494. three unsigned longs (u, v, and r). In a typical implementation, these
  17495. fractions would require 20 bytes of storage, instead of only the eight bytes a
  17496. proper design would need to store just the numerator and the denominator.
  17497. Obviously, the storage needed by the gcd function does not have to be present
  17498. in every fraction, and can be obtained (automatically) from the run-time stack
  17499. when the gcd function is called.
  17500. If we want to keep this particular gcd function separate from any other gcd
  17501. function that might be lying around in a library somewhere, it would be
  17502. acceptable to implement gcd as a member function of the fraction class. In
  17503. that case, it might be implemented as function that returns the gcd of the
  17504. denominators of the two fractions, as in
  17505. fraction3.denominator =
  17506. fraction1.gcd( fraction2 );
  17507. Inheritance is an especially attractive feature of object-oriented programming
  17508. and C++, but we must not let ourselves fall into the trap of using it
  17509. inappropriately because it usually has a cost associated with it. One simple
  17510. test that may be applied is to ask whether the new class is a special case of
  17511. the inherited class. If not, then the proposed inheritance is wrong.
  17512. Sincerely yours,
  17513. Herbert R. Haynes, Ph.D.
  17514. 6630 Pharaoh Dr.
  17515. Corpus Christi, TX 78412
  17516. Dear Editor,
  17517. Reading your journal always gives me a real pleasure. I read it from cover to
  17518. cover, but articles about programming concrete problems in C are of great
  17519. interest to me.
  17520. I should like to thank you for the series "Doctor C's Pointers" and Stuart T.
  17521. Baird's article "Using Large Arrays In Turbo C" in January 1991. It was very
  17522. useful to me. I'm also interested in the shareware of The C Users' Group, but
  17523. it's absolutely unattainable for me.
  17524. The lack of attention to programming artificial intelligence in low-level
  17525. languages, specifically to expert systems, is regrettable. If you are
  17526. interested, I could offer you a programming example of a simple rule-based
  17527. expert system in C.
  17528. It is also regrettable that The C Users Journal is not widely available in
  17529. Russia. Despite the fact that it is only accessible at a few libraries, it is
  17530. very popular in Russia. I would be very glad to expand the readers' circle of
  17531. your journal in Russia.
  17532. Warmest Regards,
  17533. Dmitry N. Ivanov
  17534. Udaltsova, 57-43
  17535. Moscow, Russia
  17536. I assure you we've made no editorial policy against AI in C. In fact, AI is
  17537. one of my personal interests. I should think a rule-based system in C would be
  17538. very interesting -- but remember, the overall quality of the manuscript is
  17539. always the determining factor in whether a story gets into print.
  17540. The biggest reason we haven't run much on AI is that we've seen few good AI
  17541. manuscripts. I take that to be an indication that AI is still finding limited
  17542. application. -- rlw
  17543. Dear Mr. Plauger,
  17544. It was with a great deal of interest that I read yet another reply to my
  17545. August 1991 request for help with defining/declaring global variables. The
  17546. comments and suggestions from both yourself and readers have been most
  17547. helpful. Dr. Purdum's method is one I have been using for quite some time: it
  17548. works well, and seems to be in common usage. The method proposed by Terence
  17549. Griffin (November 1991, p. 131) which uses a macro instead of a #ifndef/#endif
  17550. to accomplish the same end result seems to be a bit cleaner, however I've not
  17551. yet used this technique. And of course, the method you've suggested is also
  17552. very workable; however I don't favor it since it places the declaration and
  17553. definition in separate files. I suppose that this variety of solutions is just
  17554. one more example of the numerous ways to accomplish the same thing in C (and
  17555. yet another example of why C is so confusing to newcomers).
  17556. But "how to define a global variable" was not my original question. Perhaps I
  17557. did not phrase it clearly enough, but I do believe that my question is
  17558. important enough to look at again. In my example I had a large number of
  17559. source files and the customary number of global variables declared and defined
  17560. in a common header file. One particular file, containing the macro processing
  17561. functions, had a number of global (to that file only) variables defined in it.
  17562. As a matter of fact, the way the files and functions were broken down, all the
  17563. variables for macro processing were confined to that particular file -- the
  17564. functions in the other files had no need to know of the existence of any of
  17565. these variables. To me, this presented a neat and tidy package with all the
  17566. variables defined in one block.
  17567. The problem which came up was that one other function (an error trap routine)
  17568. needed to know the value of one of the macro processing variables. I presented
  17569. my solutions to this problem -- all of these work but I wasn't pleased with
  17570. any of them. I suppose it boils down to a matter of programming style and just
  17571. how defensive one wishes to be. I was looking for comments from others who've
  17572. dealt with this, surely not uncommon, problem.
  17573. Hope this clears up some of the puzzlement over the debate.
  17574. Yours truly,
  17575. Bob van der Poel
  17576. Bob van der Poel Software
  17577. P.O. Box 57
  17578. Wynndel, B.C.
  17579. Canada V0B 2N0
  17580.  
  17581.  
  17582.  
  17583.  
  17584.  
  17585.  
  17586.  
  17587.  
  17588.  
  17589.  
  17590.  
  17591.  
  17592.  
  17593.  
  17594.  
  17595.  
  17596.  
  17597.  
  17598.  
  17599.  
  17600.  
  17601.  
  17602.  
  17603.  
  17604.  
  17605.  
  17606.  
  17607.  
  17608.  
  17609.  
  17610.  
  17611.  
  17612.  
  17613.  
  17614.  
  17615.  
  17616.  
  17617.  
  17618.  
  17619.  
  17620.  
  17621.  
  17622.  
  17623.  
  17624.  
  17625.  
  17626.  
  17627.  
  17628.  
  17629.  
  17630.  
  17631.  
  17632.  
  17633.  
  17634.  
  17635.  
  17636.  
  17637.  
  17638.  
  17639.  
  17640.  
  17641.  
  17642.  
  17643.  
  17644.  
  17645.  
  17646. Building An Embedded System
  17647.  
  17648.  
  17649. Keith W. Cox
  17650.  
  17651.  
  17652. Keith Cox is currently employed at NH Research Inc. of Irvine, CA, where he is
  17653. the software lead for the S6000 project. He has over six years experience with
  17654. ATE-oriented embedded systems based on Motorola 8-and 16-bit microprocessors.
  17655. Keith was a member of Who's Who in the Computer Industry in 1989-90 and is the
  17656. president of Perfect Circle Computing, a private San Diego consulting firm.
  17657.  
  17658.  
  17659. For the conventional C programmer, creating an application consists of sitting
  17660. at a keyboard, editing some code, compiling to an executable, typing the
  17661. filename, and pressing ENTER. The operating system takes control, loads the
  17662. program into memory where it belongs, and executes. The program relies heavily
  17663. on library functions written by someone else to perform such tasks as
  17664. interfacing with the hardware and communicating with the user. It is
  17665. completely normal for the programmer to have little or no knowledge of how
  17666. these tasks are actually accomplished. If there is a problem, the underlying
  17667. support platform in most cases will announce it by sending a descriptive error
  17668. message to the monitor. The bug can be edited out, the code recompiled, and
  17669. program promptly executed again, in search of the next run time error.
  17670. If this describes your current software development method, creating an
  17671. application to run on an embedded system may pose a particular challenge. If
  17672. you now face the prospect of working in this environment, gone are the days
  17673. when the flip of a switch and a few seconds of power on testing would allow
  17674. you to begin computing to your heart's content. In an embedded system, turning
  17675. on the switch will do little for you. The best you can hope for is that the
  17676. microprocessor and its peripheral chips will receive a reset pulse of the
  17677. proper duration. After that, you become the proud operator of a collection of
  17678. warming silicon devices, many of them in an unknown state. If there is a
  17679. display, it will taunt you with gibberish. You can press the keys on the
  17680. keyboard (again, if there even is one), and nothing will change. Where do you
  17681. go from here?
  17682. Embedded systems programming is a unique niche in the software development
  17683. field. Most of today's embedded applications are sophisticated enough that a
  17684. well meaning but improperly trained hardware engineer is not up to the task of
  17685. implementing the system's intricate control schemes. On the other hand, a
  17686. programmer familiar only with data structures and control statements,
  17687. oblivious to the world of digital electronics, who knows little about the
  17688. inner workings of a microprocessor and cares less, will find himself
  17689. scratching his head in bewilderment when the time comes to make the hardware
  17690. do something. The embedded systems programmer must have his feet firmly
  17691. planted in both the hardware and software worlds to be successful.
  17692. I have suffered through many a long night laboring over unforgiving components
  17693. and their unintelligible documentation, trying to conjure up the exact
  17694. micro-incantation that would bring the inanimate chips to life. From my
  17695. mistakes I have learned a few secrets I wish I had known when I started. My
  17696. purpose here is to present some some basic techniques of embedded systems
  17697. programming by way of describing the implementation of a recently completed
  17698. project, so that you who must now make the transition to this hybrid world
  17699. might have at least some idea of where to start.
  17700.  
  17701.  
  17702. The S6000 Power Subsystem
  17703.  
  17704.  
  17705. The instrument I will use to illustrate the process of programming an embedded
  17706. system is the S6000 Power Subsystem recently released by NH Research Inc. of
  17707. Irvine, Ca. The S6000 provides modular AC and DC power and DC loads at
  17708. precisely programmable levels and states for Automated Test Systems. Included
  17709. in each production system is at least one and up to six chassis that
  17710. communicate with the modules and each other via an RS422 serial protocol. Each
  17711. of these chassis may contain up to six separate power devices.
  17712. There is one CPU board per system, which contains the main control firmware.
  17713. The board is populated by a Motorola 68000 family microprocessor, RAM, EPROM,
  17714. EEPROM, serial and GPIB interface chips, and timers. Interface is provided for
  17715. an optional front panel keyboard and 40 character by eight line LCD display.
  17716.  
  17717.  
  17718. Defining The Project
  17719.  
  17720.  
  17721. In any software development project one must first determine exactly what is
  17722. expected of the envisioned executable. From consulting the engineers
  17723. responsible for the hardware design, I learned that the CPU firmware was
  17724. required to
  17725. 1. Provide the system with an orderly power on sequence.
  17726. 2. Perform a power on self test and report the results via front panel
  17727. indicator lights.
  17728. 3. Determine the installed system configuration, compare it with the
  17729. configuration stored in EEPROM, and report discrepancies via the IEEE 488
  17730. (GPIB) bus.
  17731. 4. Command all installed modules to initialize and perform self test.
  17732. 5. Enter a continuous loop whose purposes would be primarily to maintain
  17733. system status information and supervisory control and secondarily to relay
  17734. command data from the outside world to the modules and status information from
  17735. the modules to the outside world.
  17736.  
  17737.  
  17738. The Development Environment
  17739.  
  17740.  
  17741. At the beginning of a project the programmer is faced with a number of
  17742. fundamental design questions, some of which are faced by all software
  17743. designers, and some which are unique to embedded systems. In the former
  17744. category, a programming environment and its related tools must be selected,
  17745. and in the latter it must be determined whether to use a real time kernel.
  17746. While embedded systems as recently as four or five years ago were written
  17747. almost exclusively in assembly language for the host microprocessor, C has in
  17748. recent years gained a great deal in popularity among embedded firmware
  17749. designers. There is good reason for this increased acceptance. C can be easily
  17750. adapted to embedded systems. It is versatile enough to make possible the
  17751. development of low-level hardware drivers in assembly language where
  17752. necessary, while executing the bulk of the code in a high-level language. This
  17753. makes development and debugging of firmware faster and easier than was
  17754. possible when writing exclusively in assembly language, while providing the
  17755. speed and control available only with assembler.
  17756. Today there are many excellent C programming tools available from a variety of
  17757. vendors for embedded programming. A rule of thumb to follow when investigating
  17758. the choices is to select an environment that is either intended for or makes
  17759. specific provision for firmware development. The environment chosen for the
  17760. S6000 project was the Microtec ANSI C compiler for the Motorola 680X0 family
  17761. microprocessors, but there are many others available for not only the Motorola
  17762. microprocessors but those of other manufacturers as well. The firmware
  17763. designer will find that for his purposes using one of these will be much less
  17764. baffling than using a C environment created for a specific platform. In
  17765. addition, such compilers generally come with Documentation directed
  17766. specifically to problems encountered in implementing embedded firmware.
  17767. One question a firmware designer is bound to encounter is whether to use a
  17768. real-time kernel. A real-time kernel is software developed by a third party
  17769. that provides some of the basic features of an operating system such as task
  17770. prioritization and management, interrupt handling, and basic I/O. There are
  17771. several of these available for use on a variety of host microprocessors. The
  17772. decision to use one must be weighed between their high cost (both in initial
  17773. expense and in licensing fees) and the complexity of the firmware system to be
  17774. implemented. In a relatively simple system such as the S6000, which must
  17775. switch between only two or three tasks, the expense of the real time kernel is
  17776. unjustified. On the other hand, in a system where tens or maybe hundreds of
  17777. events must be managed, the cost of implementing algorithms to handle them
  17778. might easily exceed the cost of a packaged real-time operating system. My own
  17779. experience with these systems is that they are expensive in terms of RAM, ROM,
  17780. and dollars, are impossible to troubleshoot, and that their vendors provide
  17781. less than adequate technical support. In my opinion, for small to medium
  17782. applications such as the S6000 they are not worth the trouble and expense.
  17783. With that in mind, no kernel was included in the S6000 design.
  17784.  
  17785.  
  17786. The C/Assembly Language Interface
  17787.  
  17788.  
  17789. It is rare for a completed embedded system to be programmed entirely in C.
  17790. Usually, assembly language is used at least to enter the vectors, and often to
  17791. perform the system startup and device initialization routines. Why use
  17792. assembly language instead of C? In many cases the code directly interfacing
  17793. with the hardware is required to be compact and fast. Depending on the tools
  17794. used and the expertise of the programmer, it may be possible to accomplish
  17795. this in C, but in reality the compiler is rarely able to provide assembly
  17796. language output as tight as the code you can produce yourself. Additionally,
  17797. with assembly language you have full control over the hardware and the data
  17798. presented to it, something you may not have, or that you must be very careful
  17799. to achieve, using C.
  17800. When implementing a mixed language system, the assembly-language code is
  17801. entered in separate files from the C code. Since compilation is generally a
  17802. two-step process (from C to assembler, then from assembler to object code),
  17803. the assembler simply skips the first part of the process when producing the
  17804. object code. Your makefile should be set up to instruct the development tools
  17805. how to process the assembly code. The linker will act on the object output of
  17806. the assembler in exactly the same way as the output from the C compiler,
  17807. because they are in exactly the same format.
  17808. Something to keep in mind when calling functions written in assembly from the
  17809. C code is that the compiler will often add an underscore (or in some cases a
  17810. dot) to the names of functions located in and called from a C program. For
  17811. example, a call to the function write_char(), located in an assembly language
  17812. file, will cause the instruction
  17813. jsr _write_char
  17814. to be contained in the C module's assembly-language output. If your routine in
  17815. the assembly-language file is named write_char instead of _write_char, the
  17816. linker will not know where to find it, and will generate an error. To overcome
  17817. this difficulty, equate the name of the assembly-language routine with the
  17818. name the compiler will generate to call it. For example, the line
  17819. _write_char equ write_char
  17820. at the end of the write_char routine would enable the linker to find the
  17821. correct address. An even simpler method is to merely add the underscore to the
  17822. routine's declaration in the assembly-language file.
  17823. There may be some instances when you would rather write some of the low-level
  17824. access in C, and in this case it will be necessary to know how to perform
  17825. direct memory reads and writes. The code fragment below illustrates the task
  17826. of writing a character to a hardware address.
  17827. void func(char c)
  17828. {
  17829. char *ch;
  17830. ch = (char *) 0xFFFF0400;
  17831.  
  17832. *ch = c;
  17833. }
  17834. Here, a pointer variable named ch has been declared. This variable could have
  17835. been a pointer of any type, depending on the width of the hardware port being
  17836. written (for example, short * for a 16 bit port, or long * for one that is 32
  17837. bits wide). The first code line causes the variable ch to point to address
  17838. 0xFFFF0400, an arbitrary address that could be any address in your memory map.
  17839. The second code line causes the value of c to be written to the selected
  17840. address.
  17841. Reading a memory address is performed following a similar process, as shown
  17842. below.
  17843. char func()
  17844. {
  17845. char *ch;
  17846. ch = (char *) 0xFFFF0400;
  17847. return(*ch};
  17848. }
  17849. In this case the variable ch is declared and assigned as in the write
  17850. operation. The last code line returns the 8-bit value located at address
  17851. 0xFFFF0400 to the calling function.
  17852.  
  17853.  
  17854. The Memory Map
  17855.  
  17856.  
  17857. To aid in understanding the topics that follow it may be instructive to
  17858. consider the memory map of the S6000 CPU board, which is representative of
  17859. many 680X0 implementations. Figure 1 shows that the memory map can be divided
  17860. into three sections: EPROM, RAM, and Hardware Ports. The EPROM, beginning at
  17861. the lowest physical address, contains the initial vector table and all of the
  17862. program code. The RAM, using all of the populated address space between the
  17863. EPROM and the I/O ports, contains the final vector table, all of the static
  17864. data, the ZEROVARS RAM section (which is used by the C runtime libraries), the
  17865. heap, and the stack.
  17866. For those who may be unfamiliar with the operation of the heap and stack, the
  17867. stack provides temporary storage during runtime (such as for local variables),
  17868. and grows from the highest address in RAM down toward the end of the static
  17869. data. The heap consists of all unused RAM space and grows up from the end of
  17870. the static data area toward the stack. The heap is the dynamic memory area
  17871. managed by such functions as malloc() and free(). Improper management of this
  17872. area may result in the infamous and unrecoverable Heap/Stack Collision error,
  17873. for obvious reasons.
  17874.  
  17875.  
  17876. The Vector Table
  17877.  
  17878.  
  17879. When programming an embedded system the first code to be written is normally
  17880. the vector table. This table is a series of pointers in a specific location in
  17881. the microprocessor's address space. The vector table defines the system's
  17882. fundamental behavior. The beginning of the 680X0's vector table is always
  17883. located at physical address 0x00000000. Microprocessors are designed in such a
  17884. way that when certain events occur control is passed to code pointed to by one
  17885. of the vectors. For example, after the power-on reset pulse is applied to the
  17886. 68000 microprocessor's reset pin, the Program Counter (PC) will automatically
  17887. be filled with the contents of address 0x00000004 (the second vector, since
  17888. each vector is four bytes wide), and program execution will begin at that
  17889. address. The microprocessor will know where the initial stack is located, and
  17890. where to find code to execute in case of such events as bus error, address
  17891. error, illegal instruction, and divide by zero, by values located at
  17892. predetermined addresses in the vector-table. A description of the vector table
  17893. contents can be found in the documentation provided with the microprocessor.
  17894. Although the vector table is located at address 0x00000000 in the physical
  17895. address space and must initially be located in EPROM, many hardware designers
  17896. redirect the vectors (other than the first two) to addresses in RAM. This
  17897. allows the firmware designer to change the contents of the vector table on the
  17898. fly, a feature which can be useful in a variety of cases, one of which is
  17899. described later in this article.
  17900. How do you actually write the vector table? The easiest way is to write it in
  17901. assembly language as shown in Listing 1. At the top of the assembly language
  17902. file, statements instruct the assembler that certain names (of functions and
  17903. interrupt service routines) are to be found elsewhere. With most 680X0
  17904. assemblers, this is done using the xref directive. Thus the assembler makes
  17905. space for the vector table without actually knowing the actual data that will
  17906. ultimately reside there. The linker will fill in the details at link time.
  17907. After listing all external references, the ORG statement is used to tell the
  17908. assembler where to put the code it is about to encounter (0x00000000).
  17909. Following the ORG statement, all the vectors are defined using the define long
  17910. constant directive or its equivalent for the assembler in question. At compile
  17911. time, this module is assembled to a .obj or equivalent file which the linker
  17912. can include in the final executable.
  17913.  
  17914.  
  17915. The Application Startup Code
  17916.  
  17917.  
  17918. The startup code begins at the address pointed to in the Initial PC vector.
  17919. The purpose of this code will vary between applications. In the S6000, the
  17920. startup process performs various self tests and initializes the hardware ports
  17921. and devices.
  17922. Because the hardware is in an unknown state at power on, it is wise before
  17923. performing any other task to mask out external interrupts while initializing
  17924. the system. This is accomplished by the first instruction in the startup code.
  17925. The contents of the vector table are then copied to the first 400 (hex)
  17926. addresses in RAM so that they can be changed as needed. Once this has been
  17927. accomplished, a check is made to see how much RAM is installed in the system.
  17928. The CPU board is designed in such a way that the amount of RAM it can hold
  17929. varies between 16K bytes and 256K bytes in 16K byte blocks. It is important to
  17930. know the actual amount installed so that the address of the top of the stack
  17931. can be registered in the microprocessor. Determining the amount of RAM present
  17932. is accomplished by writing a value to the first address of each 16KB boundary.
  17933. If RAM is present, the address counter is incremented by 16KB and the
  17934. operation repeated until the maximum address is reached. If there is no RAM
  17935. present at any address being written to, a bus error occurs, and code at the
  17936. address entered in the Bus Error vector is executed. In the S6000 this
  17937. interrupt service routine assigns the stack pointer in the SP (A7) register to
  17938. the value found by this process and changes the bus error vector (now located
  17939. in RAM) to the value of the real Bus Error interrupt service routine.
  17940. The startup code then performs a check of RAM and EEPROM memory, reporting any
  17941. errors, and each of the initialization routines for the specific hardware
  17942. devices are called. These routines might be provided by the hardware vendor or
  17943. a third party, but most likely the system designer will have to create them.
  17944. For the S6000, initialization and I/O routines were designed and implemented
  17945. in assembly language for GPIB I/O, serial I/O, timer interrupt management,
  17946. keyboard input, and display output.
  17947. Finally, the external interrupts are unmasked, and control is passed to
  17948. routines that will initialize the C runtime environment.
  17949.  
  17950.  
  17951. The C Runtime Environment
  17952.  
  17953.  
  17954. When programming for a platform such as a PC, which conforms to a hardware
  17955. standard and for which the operating system is known to be present, a C
  17956. compiler will add code at compile time that defines an environment for the C
  17957. program to work in. This code performs initialization of the heap and makes
  17958. assumptions about where data is likely to come from (the keyboard) and go to
  17959. (the screen). Using these suppositions the compiler provides elementary code
  17960. fragments to handle memory allocation, keyboard input, and screen output (the
  17961. devices stdin for standard input, stdout for standard output, and stderr for
  17962. standard error). In an embedded system, these presumptions cannot be made,
  17963. since there may not be a keyboard, screen, or heap. Therefore, in order to be
  17964. able to use standard library functions such as malloc and printf the
  17965. programmer is obliged either to provide these routines or modify those
  17966. provided with the environment.
  17967. To take full advantage of the C run-time library it will be necessary to
  17968. accomplish the following steps:
  17969. 1. The heap pointer must be assigned to tell the compiler where the heap is
  17970. located.
  17971. 2. The ZEROVARS section must be cleared.
  17972. 3. The standard input, output, and error devices must be initialized.
  17973. Additionally, if file I/O is to be accomplished, the file structures must be
  17974. assigned and initialized.
  17975. 4. Code must be provided to access the devices which input and output
  17976. characters, and the library routines must know where to find it.
  17977. To locate the initial heap pointer in the S6000 a four-byte variable is
  17978. declared in a section called HEAP. Using almost any linker the order of the
  17979. code and data sections can be specified. In the case of the Microtec tools
  17980. this is done using a linker command file. In this file, the HEAP section is
  17981. declared to be last, after the ZEROVARS section, since the highest addresses
  17982. the linker will be concerned with are located in RAM. This will cause the
  17983. address of the variable declared in this section to be located at the end of
  17984. the static variables used by the program. Thus, the address of this variable
  17985. is in reality the first address of the unassigned memory between the static
  17986. data area and the stack, or the heap. This address is assigned to a constant
  17987. variable (called ????HEAP in the Microtec environment) that tells the compiler
  17988. where the dynamic memory area begins.
  17989. Static variables that need to be cleared at the beginning of the program are
  17990. located in the ZEROVARS section. Among them are various heap-management
  17991. pointers. In the Microtec environment, if the application intends to use
  17992. dynamic memory allocation it is important that this area be filled with zeros,
  17993. otherwise memory allocation will not work properly, probably with disastrous
  17994. results. The ZEROVARS section is cleared as a matter of course in systems
  17995. where the program startup code is provided by the compiler. Although the code
  17996. to do this is usually available to the embedded system programmer, it is not
  17997. automatically included in the program or invoked. It is up to the programmer
  17998. to locate the code and incorporate it in the program. If the development
  17999. environment selected is meant for embedded systems programming, the compiler's
  18000. documentation should tell how to do this. If not, the code to do this will be
  18001. among the startup code fragments provided with the compiler, and the system
  18002. developer will need to peruse these files to find it and include it in the
  18003. final executable. In the Microtec environment, this code is located in a file
  18004. called ENTRY.S.
  18005. In the Microtec compiler for the 68000 family there is a file called CSYS68K.C
  18006. which contains the code for performing the rest of the startup sequence. The
  18007. _START routine in this file initializes the heap pointers for malloc() and
  18008. opens the stdin, stdout, and stderr devices. If different compiler tools are
  18009. being used, there should be a file or files that will perform these same
  18010. functions, again, hopefully described in the documentation.
  18011. Additional skeleton functions must be provided for handling character input
  18012. and output. The tasks consist of extracting characters from the proper
  18013. hardware address and returning them to the calling function, or receiving
  18014. characters for output from the library routines and actually writing their
  18015. value to the proper address in memory. These routines are used by such library
  18016. functions as printf and getch. Without them the library functions cannot work
  18017. properly. It is possible to bypass the library functions, however this
  18018. generally will make the programmer's job more difficult instead of less,
  18019. because he will have to provide routines that perform tasks that have already
  18020. been implemented.
  18021.  
  18022.  
  18023. The Main Program
  18024.  
  18025.  
  18026. At the end of the startup activities, control is finally passed to the
  18027. program's main() function. The programmer may assume that if the above steps
  18028. have all been done correctly and the code can be traced to the address of
  18029. main(), writing the rest of the program will be very much the same as writing
  18030. any other C program. Nevertheless, certain precautions should be adhered to
  18031. which the programmer may not (but probably should) observe in other
  18032. programming environments.
  18033. Make sure that no variable is used that has not been explicitly initialized.
  18034. The startup code cannot be relied on to zero all of the data space unless the
  18035. programmer specifically directs it. Also, initialize variables in the body of
  18036. the code rather than in the declaration. Some complex variables that are
  18037. initialized in the declaration may be considered to be constants and placed in
  18038. ROM, an unhappy situation that can be difficult to troubleshoot.
  18039.  
  18040. A last word of advice to the would-be embedded systems programmer is to become
  18041. familiar with the hardware involved. The idea of learning digital hardware is
  18042. often distasteful to software engineers but the knowledge can be indispensable
  18043. when debugging embedded code. All hardware devices are documented, some better
  18044. than others, and most programmable chips contain sections aimed specifically
  18045. at firmware developers. Because hardware designers are often unfamiliar with
  18046. the devices themselves, and because errors frequently occur in preparing
  18047. prototypes, becoming familiar with the hardware in question can save the
  18048. embedded systems programmer countless hours of debugging a problem which may
  18049. in the end be caused by improper hardware implementation.
  18050. Embedded systems programming, while perhaps not as glamorous as other aspects
  18051. of software development, can be an interesting and rewarding experience. With
  18052. the knowledge gained from this endeavor the software designer will become more
  18053. articulate with the actual inner workings of computers, which will make even
  18054. unrelated programming tasks easier to understand and implement.
  18055. Figure 1
  18056.  
  18057. Listing 1
  18058. .
  18059. .
  18060. .
  18061. xref start
  18062. xref aerr
  18063. xref berr
  18064. xref illegal
  18065. xref div0
  18066.  
  18067. ORG 0
  18068.  
  18069. vctr000 dc.l STACKTOP * the top of the stack
  18070. vctr001 dc.l start * the startup code address
  18071. vctr002 dc.l aerr * the address error vector
  18072. vctr003 dc.l berr * the bus error vector
  18073. vctr004 dc.l illegal * the illegal instruction vector
  18074. vctr005 dc.l div0 * the divide by 0 vector
  18075. .
  18076. .
  18077. .
  18078.  
  18079.  
  18080.  
  18081.  
  18082.  
  18083.  
  18084.  
  18085.  
  18086.  
  18087.  
  18088.  
  18089.  
  18090.  
  18091.  
  18092.  
  18093.  
  18094.  
  18095.  
  18096.  
  18097.  
  18098.  
  18099.  
  18100.  
  18101.  
  18102.  
  18103.  
  18104.  
  18105.  
  18106.  
  18107.  
  18108.  
  18109.  
  18110.  
  18111.  
  18112.  
  18113.  
  18114.  
  18115.  
  18116. OOP Without C++
  18117.  
  18118.  
  18119. Bill Bingham, Tom Schlintz, and Greg Goslen
  18120.  
  18121.  
  18122. Tom Schlintz has a BSEE from Virginia Polytechnic Institute and has been
  18123. working as a software engineer for Logical Design group for the past four
  18124. years. His Interests are embedded and VME bus-based applications. Greg Goslen
  18125. has a BA in Physics from Appalachian State University and has been working as
  18126. a software engineer for Cardiovascular Diagnostics for the past year. He has
  18127. nine years of embedded systems experience, four of them working in C. Bill
  18128. Bingham has a BA in Biology from The University of Virginia, an MSEE from
  18129. Berkeley, and MEng Bioengineering from Berkeley. He is currently the senior
  18130. research and development engineer at Cardiovascular Diagnostics, where he has
  18131. worked for the past two years.
  18132.  
  18133.  
  18134. When most programmers think of object-oriented programming (OOP), they think
  18135. of object-oriented languages. What we often forget is that OOP is more than
  18136. just using a type of compiler. It is a programming concept -- a way of
  18137. thinking about the way we structure our code.
  18138. We're writing this paper to demonstrate three things: first, that OOP isn't as
  18139. scary as you may have been led to believe; second, that no one person, group,
  18140. or manufacturer has an exclusive on the implementation of OOP; and third, how
  18141. we addressed a small embedded systems project, using an OOP approach, and made
  18142. it work.
  18143. Several months ago we were struggling with the code for a new medical device.
  18144. We required that the code be clear, modular, and thoroughly testable, an ideal
  18145. candidate for object-oriented programming techniques. Unfortunately, it also
  18146. had to perform a large number of tests in limited ROM (48KB), limited RAM
  18147. (8KB), and on a processor built for small, embedded applications, the 68HC11.
  18148. To further complicate matters there was neither an OOP compiler for our
  18149. platform nor the code space to support a full-blown OOP strategy. Still, we
  18150. wondered if we could use a standard, high level language to get some of the
  18151. benefits of OOP without sacrificing too much space.
  18152. Once we began this project, the first thing we noticed was that we already
  18153. knew OOP, we just didn't know that we knew. The concepts are not difficult, we
  18154. had all used some of them before. The major difference is that in an OOP
  18155. language the constructs are supported by the compiler.
  18156.  
  18157.  
  18158. Implementation
  18159.  
  18160.  
  18161. The classic object-oriented system creates objects in RAM, allows them to
  18162. communicate with one another and selectively destroys them once they have
  18163. finished their tasks. In an embedded system, RAM is precious, so we created
  18164. object classes manually using typedefed structs, which we stored, along with
  18165. object definitions and pointers to object methods, in ROM. In essence we did
  18166. manually what an OOP compiler does for you.
  18167. To further conserve ROM space we used the concept of inheritance to eliminate
  18168. redundancies between tests. We began by creating a parent class, TEST_CLASS as
  18169. a typedefed struct, to be the master template for our test objects. It is
  18170. defined in the header file class.h, as shown in Listing 1, and is fixed at
  18171. compile time. Listing 2 shows how, by creating variables of type TEST_CLASS,
  18172. several objects, test_a, test_b, and test_c, inherit their basic structure
  18173. from it.
  18174. To further conserve ROM we created several subclasses of TEST_CLASS. We placed
  18175. class specific methods and data into a common module and object specific
  18176. methods and data into object specific modules. Objects of a given subclass
  18177. inherit data and methods from that subclass.
  18178. Thus, test_a and test_b are of the same subclass, class 1, and both use
  18179. class_1_display, which is defined in Listing 3, class_1.c. This is an example
  18180. of inheritance from a subclass. test_c is of a different subclass, class 2,
  18181. and so inherits class_2_display. All the test objects define their own public
  18182. data, e.g., Test_Name, and public methods, e.g., init_object.
  18183. Using our protocol the names of all methods are fixed when a class is created.
  18184. When a subclass inherits method names from the parent class we manually attach
  18185. one of several functions to the pointer associated with that method name. By
  18186. using this form of polymorphism we reduce the complexity of the main program
  18187. and force implementation specific details to remain encapsulated within the
  18188. objects.
  18189. Our project specified twenty different tests, and that adding, changing, or
  18190. deleting a test be easy and have no impact on existing tests. To achieve this
  18191. we created a master array of pointers, test_ptr[], in the main program file.
  18192. The elements of the master array point to the structure defining each test
  18193. object. An index into test_ptr[] yields a pointer to the selected test object
  18194. (see Listing 4). The selected object then performs the test via its methods.
  18195. In order to compile properly, test_a, test_b, and test_c must be declared as
  18196. externals above the declaration of test_ptr[]. The last, null entry in
  18197. test_ptr[] is used as a termination to allow any number of tests to be
  18198. incorporated. The Test_Name[] entry in every object of type TEST_STR allows
  18199. the main program to search for the correct object by matching Test_Name to a
  18200. string.
  18201. The invoking program doesn't need to know which routine it is using, only
  18202. that, for example, it is calling a display method. The display method handles
  18203. the details. This makes it easy to add tests. It may seem like this is a lot
  18204. of work to go through, when it would be much simpler to completely define test
  18205. objects as structures throughout the program. If, however, we wish to add a
  18206. new test with new functionality, this approach allows us to create new methods
  18207. without changing data flow.
  18208. Since we were already using separate modules to implement inheritance we
  18209. decided we could go further and use the C specification to implement
  18210. information hiding. C forces knowledge of variables declared at the beginning
  18211. of a file to be confined to the routines in that file. This allowed us to
  18212. define static variables at the top of the file that are freely accessible to
  18213. all routines within the file, while hiding these variables from routines
  18214. outside. Outside access to the data can be tightly controlled by routines
  18215. within the file. We used this aspect of C to treat the contents of a file as
  18216. an object, and the routines and data within the file as methods and private
  18217. data of that object. By defining an object as files only the methods defined
  18218. in that file have access to the object data.
  18219. We recognized that data hiding necessitated some form of messaging. What we
  18220. needed was a flexible and not too test specific, messaging scheme. Each test
  18221. expected a different set of inputs and produced a different set of results. If
  18222. these parameters were declared explicitly in the test method argument list,
  18223. each test would have had to be a separate class. On the other hand, if we had
  18224. used TEST_CLASS to define all of the tests, all instances of a given method
  18225. would have had identical argument lists.
  18226. Our solution was to establish global, generic floating-point arrays that could
  18227. be passed as parameters to any method of the current test object (see Listing
  18228. 5). In our case all data could be passed as floating point but a more generic
  18229. interface could be constructed using an array of strings. Structures were
  18230. typedefed at the top of each object file which described the format used by
  18231. all the methods of that object. The objects used the structure to encode or
  18232. decode the information stored in the arrays.
  18233. The parameter array is allocated in global RAM and is bigger than the maximum
  18234. number of parameters for any test. A similar array of floats is defined for
  18235. passing results back from tests. The main program passes the pointers to the
  18236. params and results arrays to the methods when it invokes them and never knows
  18237. what is in these arrays. Its job is to coordinate the passing of the arrays
  18238. from one method to the next. The arrays combined with the object specific
  18239. template act like a secret decoder ring available only to the methods within
  18240. the object and allowed us to use meaningful names instead of index numbers to
  18241. refer to each parameter. The result was much clearer code.
  18242. Listing 6 shows a test called test_a which requires that quantities called
  18243. time and amplitude be passed to it. Notice that the parameter template maps
  18244. the first two elements of params to these names. This template resides in the
  18245. class specific module file (class_1.h shown in Listing 6).
  18246. The methods shown in Listing 3 take these pointers and cast them with the same
  18247. class specific template into a pointer to a structure of type PARAMETER_STR.
  18248. In Listing 6 the method casts the PARAMETER_STR to the method specific
  18249. pointer, ps. The method can now interpret the parameter string values
  18250. according to its own definition and can refer to any of the parameters by
  18251. name, as shown in Listing 6. A similar mechanism is used to place results in
  18252. the results array.
  18253.  
  18254.  
  18255. Summary
  18256.  
  18257.  
  18258. The effort we expended to implement OOP in our system has been paid back by
  18259. the ease of adding new tests to the system, the disappearance of cross-test
  18260. interference, and greatly decreased debugging time. Once implemented, OOP
  18261. techniques helped us to achieve a higher level of abstraction. Because
  18262. implementation details were left up to the private methods within each of our
  18263. objects, our code could ignore how things were implemented on lower levels.
  18264. This freed us to concentrate on the design aspects of the problem.
  18265. We would like to thank Murdock Taylor for developing the hardware platform we
  18266. used in this project.
  18267. Basic OOP Terms
  18268. Before we could understand OOP we had to become familiar with the language.
  18269. Understanding each of these terms, and particularly what separates theory from
  18270. practice, was what allowed us to use them in our design.
  18271. Method: A function by any other name. A method is code bound to an object. The
  18272. code provides an interface for messages requesting an operation and a means to
  18273. perform the requested operation.
  18274. Object: A conceptual entity, implemented in software, which has distinct
  18275. boundaries and contains both data and code. There are two types of code in an
  18276. object -- methods, which transfer data across the object's boundaries, and
  18277. internal functions, which provide private services to the object.
  18278. Class: A class is the template from which an object is derived. It defines
  18279. what data, functions and methods can be bound to the object. An object is
  18280. formed by creating an instance of the class and filling in the blanks.
  18281. Inheritance: This is the ability to derive a new class from an existing one.
  18282. The child class can be a sub or super set of the parent. Multiple Inheritance
  18283. is the ability to derive a new class from more than one parent class.
  18284. Polymorphism: Polymorphism is when two objects bind a different method but
  18285. access it by the same name. The classic example is to create two classes, car
  18286. and boat. An object from either class can perform an operation "Move" but the
  18287. method which carries out the operation is different. The use of the same
  18288. command to generate similar effects using different code is polymorphism.
  18289. Information Hiding: Information hiding denies outside entities direct access
  18290. to the object's private data and functions. Instead it forces outsiders to
  18291. access the data in a controlled fashion via the object's methods. As with some
  18292. government agencies, if you don't need to know, your knowing will gum the
  18293. works.
  18294. Messaging: The act of communicating with an object to get something done. It
  18295. consists of invoking a method, supplying that method with the information it
  18296. requires, and receiving a reply from the method. Clearly, messaging can be
  18297. something as simple as a function call.
  18298.  
  18299. Listing 1 (class.h)
  18300. /****************************************************
  18301. * This is the definition of the class, TEST_CLASS.
  18302. * It is defined as a typedefed struct. It possesses
  18303. * public data: Test_Name, and public methods:
  18304. * init_object, process_data and disp_results.
  18305. * This object is compiled into ROM.
  18306. ****************************************************/
  18307.  
  18308.  
  18309. typedef struct {
  18310.  
  18311. char Test_Name[8];
  18312. void (*init_object) (void);
  18313. void (*process_data) (float *parameters, float
  18314. *results);
  18315. void (*disp_results) (float *results);
  18316.  
  18317. } TEST_CLASS;
  18318.  
  18319. /* End of File */
  18320.  
  18321.  
  18322. Listing 2
  18323. /****************************************************
  18324. * This code declares tests objects, test_a, test_b
  18325. * and test_c. These objects attach their own
  18326. * initialization, processing and display routines
  18327. * to the pointers provided.
  18328. * Each test has its own name and initialization methods,
  18329. * but inherits its store and display methods from it's
  18330. * respective class, ie. test_b from class_1, test_c from
  18331. * class_2.
  18332. *
  18333. * Construct Name Location
  18334. *
  18335. * CLASS TEST_CLASS class header file ROM
  18336. * SUBCLASS class_1, class_2 class specific files ROM
  18337. * OBJECTS test_a, test_b object specific files ROM
  18338. *
  18339. * The "const" directive tells the compiler to place
  18340. * the test objects in ROM.
  18341. ****************************************************/
  18342.  
  18343. file test_a.c
  18344.  
  18345. /***** Definition of Object "A" *******/
  18346.  
  18347. const TEST_CLASS test a = { "TEST A",
  18348. object_a_init,
  18349. class_1_process_data,
  18350. class_1_display
  18351. };
  18352.  
  18353. file test_b.c
  18354.  
  18355. /*****Definition of Object "B" ********/
  18356.  
  18357. const TEST_CLASS test_b = { "TEST B",
  18358. object b_init,
  18359. class_1_process_data,
  18360. class_1_display
  18361. };
  18362.  
  18363.  
  18364. file test_c.c
  18365.  
  18366. /***** Definition of Object "C" ********/
  18367.  
  18368.  
  18369. const TEST_CLASS test_c = { "TEST C",
  18370. object c_init,
  18371. class 2_process_data,
  18372. class_2_display
  18373. };
  18374.  
  18375. /* End of File */
  18376.  
  18377.  
  18378. Listing 3
  18379. /****************************************************
  18380. * Shows two class 1 specific methods, class_1_display
  18381. * and class_1_process_data, defined in the class 1
  18382. * module. It also shows an object specific method,
  18383. * object_a_init, defined in the object module,
  18384. * test_a.c
  18385. *
  18386. * This code is compiled to ROM.
  18387. ****************************************************/
  18388.  
  18389. file class_1.c
  18390.  
  18391. void class_1_display(float *results)
  18392. {
  18393. - code -
  18394. }
  18395.  
  18396. void class_1_process_data(float *parameters, float *results)
  18397. {
  18398. - code -
  18399. }
  18400.  
  18401.  
  18402. file test_a.c
  18403.  
  18404. void object_a_init(void)
  18405. {
  18406. - code -
  18407. }
  18408.  
  18409. /* End of File */
  18410.  
  18411.  
  18412. Listing 4 (main.c)
  18413. /****************************************************
  18414. * This is an example of the array of pointers to test
  18415. * objects. It shows how the array is defined and how
  18416. * it can be searched for a specific test object. It
  18417. * also shows how, once the index to the correct test
  18418. * object is found, the pointer to the test object can
  18419. * be used to invoke the object's methods.
  18420. *
  18421. * This code is compiled to ROM.
  18422. ****************************************************/
  18423.  
  18424. extern TEST_STR test_a();
  18425. extern TEST_STR test_b();
  18426. extern TEST_STR test_c();
  18427.  
  18428.  
  18429. TEST_CLASS *test;
  18430. TEST_CLASS (const *test_ptr[]) = {&test_a,
  18431. &test b,
  18432. &test_c,
  18433. 0};
  18434.  
  18435. /* assume name is a parameter set to */
  18436. /* "Test A" select the Test A test object */
  18437.  
  18438. test = test_ptr[0];
  18439. while(test != 0) {
  18440. if(strcmp(test->Test_Name,name) == 0) break;
  18441. test++;
  18442. }
  18443.  
  18444. /* and use it to run the test */
  18445. .
  18446. .
  18447. test->init_object();
  18448. .
  18449. .
  18450. test->process_data(params,results);
  18451. .
  18452. .
  18453. test->disp_results(results);
  18454. .
  18455. .
  18456.  
  18457.  
  18458. /* End of File */
  18459.  
  18460.  
  18461. Listing 5
  18462. /****************************************************
  18463. * This is an example of global, generic floating point
  18464. * arrays which are used to pass parameters
  18465. *
  18466. * This code is compiled to RAM.
  18467. ****************************************************/
  18468.  
  18469. file main.h
  18470.  
  18471.  
  18472. #define NUMPARAMS 20
  18473. #define NUMRESULTS 12
  18474.  
  18475. file main.c
  18476.  
  18477. float params[NUMPARAMS];
  18478. float results[NUMRESULTS];
  18479.  
  18480. /* End of File */
  18481.  
  18482.  
  18483. Listing 6
  18484. /****************************************************
  18485. * This is an example of messaging between methods.
  18486. * The methods use an object specific template to
  18487. * encode/decode messages being passed.
  18488.  
  18489. *
  18490. * This code is compiled to ROM.
  18491. ****************************************************/
  18492.  
  18493.  
  18494. file class_1.h
  18495.  
  18496.  
  18497. typedef struct {
  18498.  
  18499. float time;
  18500. float amplitude;
  18501.  
  18502. } PARAMETER_STR;
  18503.  
  18504. typedef struct {
  18505.  
  18506. float result_1;
  18507. float result_2;
  18508. float result_3;
  18509.  
  18510. } RESULT_STR;
  18511.  
  18512.  
  18513. file test_a.c
  18514.  
  18515.  
  18516. #include class_1.h
  18517.  
  18518. int test_a_process_data (float *params, float *results) {
  18519.  
  18520. float value_1, value_2, value_3;
  18521. float time, amp;
  18522.  
  18523. PARAMETER_STR *ps;
  18524. RESULT_STR *rs;
  18525. .
  18526. .
  18527. /* apply the template to the */
  18528. /* results and parameter arrays */
  18529.  
  18530. ps = (PARAMETER_STR *) params;
  18531. rs = (RESULT_STR *) results;
  18532. .
  18533. .
  18534. .
  18535. time = ps->time;
  18536. amp = ps->amplitude;
  18537. .
  18538. /* test specific calculations */
  18539. .
  18540. rs->result_1 = value_1;
  18541. rs->result_2 = value_2;
  18542. rs->result_3 = value_3;
  18543.  
  18544. }
  18545.  
  18546. /* End of File */
  18547.  
  18548.  
  18549.  
  18550.  
  18551.  
  18552.  
  18553.  
  18554.  
  18555.  
  18556.  
  18557.  
  18558.  
  18559.  
  18560.  
  18561.  
  18562.  
  18563.  
  18564.  
  18565.  
  18566.  
  18567.  
  18568.  
  18569.  
  18570.  
  18571.  
  18572.  
  18573.  
  18574.  
  18575.  
  18576.  
  18577.  
  18578.  
  18579.  
  18580.  
  18581.  
  18582.  
  18583.  
  18584.  
  18585.  
  18586.  
  18587.  
  18588.  
  18589.  
  18590.  
  18591.  
  18592.  
  18593.  
  18594.  
  18595.  
  18596.  
  18597.  
  18598.  
  18599.  
  18600.  
  18601.  
  18602.  
  18603.  
  18604.  
  18605.  
  18606.  
  18607.  
  18608.  
  18609.  
  18610.  
  18611.  
  18612. The Device Driver As State Machine
  18613.  
  18614.  
  18615. Thomas Nelson
  18616.  
  18617.  
  18618. Tom Nelson is an independent author, consultant, and part-time artist. You can
  18619. reach him at 5004 W. Mt. Hope Rd., Lansing, MI 48917.
  18620.  
  18621.  
  18622. A state machine is a useful logical construct for writing device control code.
  18623. By mirroring the device's actual operation, the state machine makes the code
  18624. easier to comprehend and therefore modify. Since a state machine usually
  18625. relies heavily on initialized data in tables, the amount of logical branching
  18626. in the associated code is minimized, thus helping the programmer avoid
  18627. "spaghetti" code. When properly employed, a state machine adds a layer of
  18628. abstraction between the user and the controlled device. It also isolates
  18629. device-dependent code within a limited number of functions.
  18630. From the point of view of an operating system, a device driver must present a
  18631. consistent interface to application programs that access devices you may
  18632. upgrade at any time or even to devices not yet on the market. The standard,
  18633. installable device driver does all this and more. It functions as an
  18634. extensible, user-modifiable part of an operating system.
  18635. This article attempts to combine what is necessary (the device driver) and
  18636. what is desirable from a coding standpoint (the state machine). The result is
  18637. a method of coding DOS device drivers (either PC-DOS or MS-DOS) that provide a
  18638. programmatic interface to a finite-state machine. The article presents a
  18639. working device driver that controls an idealized tape backup unit. I left out
  18640. the specifics of controlling an actual device (such as port assignments and
  18641. bit settings) to focus on the connections you must make between an actual
  18642. device and your application program.
  18643.  
  18644.  
  18645. Driver Design Considerations
  18646.  
  18647.  
  18648. Designing a DOS device driver is a relatively simple task, since any driver
  18649. must follow a standardized design. Getting one to actually run is another
  18650. matter. Many consider device drivers a specialized systems task, one to be
  18651. tackled only by the programming elite. However, the art need not be
  18652. mysterious, especially when you code mostly in C. I organized Listing 1 and
  18653. Listing 2, which form the kernel of a DOS device driver, as a template from
  18654. which you can build any device driver. The template lets you concentrate on
  18655. the actual device controller code. If you use the Borland compiler/assembler
  18656. combination, you don't even have to know assembly language, although some
  18657. knowledge of it will help you follow the ensuing discussion. If you use
  18658. another compiler and assembler, you will need to modify the code to some
  18659. extent.
  18660. I'll touch on a few basics of device drivers, but will concern myself mainly
  18661. with some important design considerations in coding a driver in C, as well as
  18662. some usage caveats. Many journal articles and book chapters deal with device
  18663. drivers. I refer the reader to the References section.
  18664.  
  18665.  
  18666. The Template
  18667.  
  18668.  
  18669. Listing 1, start.asm, presents the assembly language start-up code. At this
  18670. level, you must use assembly language in raw form (even inline assembly won't
  18671. do) because C can't provide the necessary control. All drivers consist of one
  18672. segment up to 64Kb in length, forcing all code and data into a single segment.
  18673. Some compilers can produce a tiny model .COM file that satisfies these
  18674. requirements. However, a device driver is a binary image file with an origin
  18675. of (i.e., no Program Segment Prefix block), the first bytes of which are not
  18676. executable. I believe few compilers could deal with such a situation.
  18677. Listing 1 begins by defining segments common to both the Borland and Microsoft
  18678. compilers. If your compiler uses different segment names, you must change
  18679. them. The segments must also be defined in the proper order. You must place
  18680. start.obj first in the link order so that segments declared in other modules
  18681. will be ordered the way you specify here (see the MAKE file in Listing 7). A
  18682. compiler will usually define DGROUP (in the small model default) as a group of
  18683. all data segments, keeping the code segment separate. Here I define DGROUP by
  18684. combining both code and data segments, the same as for a tiny model .COM
  18685. program.
  18686. I also defined another segment called ENDSEG, which is also part of DGROUP and
  18687. will be linked in behind all other segments. The offset to ENDSEG allows DOS
  18688. to calculate the driver's size (the break address) when DOS loads another
  18689. driver behind yours.
  18690. As mentioned earlier, all drivers have an origin of 0, specified by the org 0
  18691. statement. You must place the driver's header block immediately after, at
  18692. offset in . The four-byte pointer field in the header usually contains -1L
  18693. (0xffffffff) unless your file contains code for more than one driver, in which
  18694. case it points to the next driver's header block. Whatever the case, the
  18695. pointer in the last driver's header block must contain -1L. DOS fills it with
  18696. a pointer to the next driver's header in the chain at driver load time.
  18697. DOS always communicates with its device drivers using a curious two-step call.
  18698. Most authors assume this was intended as a bridge to the future when DOS would
  18699. support full multitasking. In view of the present status of DOS, multitasking
  18700. is at best a remote possibility. The first part of this two-step call, the
  18701. _dev_strategy() routine, merely saves a pointer to the caller's request header
  18702. block. To maintain backward compatibility, all DOS drivers must include this
  18703. routine, which represents a bit of additional overhead.
  18704. The real work of the driver is performed by the second of the two procedures
  18705. called by DOS, the interrupt routine. Although not a true interrupt handler,
  18706. it shares many characteristics with them. The main difference is that it
  18707. terminates with a far ret rather than an iret. Like an interrupt handler,
  18708. however, the routine saves the CPU state and performs other housekeeping
  18709. chores. If you're going to support compiled C code, the startup procedures
  18710. must occur here also. Your startup code must duplicate as closely as possible
  18711. the environment created by the compiler's normal startup code. The difference
  18712. is that startup occurs on every call to the driver, not just once when DOS
  18713. executes an application.
  18714. After saving the CPU state and caller's stack context, the interrupt routine
  18715. reclaims its own data segment, contained in the CS register. Since code and
  18716. data are contained in one segment, the routine loads the DS register with the
  18717. same value. Loading ES is probably optional, since most DOS compilers seem to
  18718. make no assumptions regarding ES.
  18719. The interrupt routine sets up its own internal stack by loading the SS:SP
  18720. register pair with the appropriate values. One difference here is that the
  18721. driver's stack does not have its own segment, as is usually the case. The
  18722. linker expects a separate stack segment, however, and will complain about it.
  18723. This is one warning you can ignore. Although creating a separate stack for the
  18724. driver is not strictly necessary, supporting a stack-intensive language like C
  18725. makes separate stacks highly desirable. (I have seen attempts to program
  18726. device drivers in C where all variables were declared global to avoid blowing
  18727. the short stack provided by DOS, reportedly as short as 40-50 bytes.)
  18728. Providing your device driver with its own stack is simple, and the benefits
  18729. easily outweigh the small overhead in space. If you're really worried about
  18730. memory usage, you should code the whole device driver in assembly language.
  18731. Having set up the appropriate machine environment, the interrupt routine then
  18732. branches into C code. It pushes a far pointer to the caller's request header
  18733. (saved earlier by the strategy routine) and calls exec_command() in Listing 2.
  18734. exec_command() serves as a central dispatch point where the call branches to
  18735. the appropriate function, or command code routine. exec_command() uses the
  18736. jump table (*Dispatch[])(), an array of pointers to the command code routines.
  18737. Using a jump table seems more elegant and less code intensive than the more
  18738. cumbersome switch statement.
  18739. exec_command() also makes the caller's request header, defined in Listing 3,
  18740. available globally, assigning it to the pointer Rh. exec_command() branches to
  18741. the correct command code routine based on the command code Rh->cmd passed in
  18742. the request header. The function uses the command code as an index into the
  18743. Dispatch jump table.
  18744. exec_command() expects all command code routines to return zero if they
  18745. execute normally. If errors arise while a command code routine executes, the
  18746. routine returns IS_ERROR plus the appropriate error code, defined in Listing
  18747. 3. On return from the command code routine, exec_command() assigns the
  18748. routine's return code to the request header. exec_command() also sets the done
  18749. bit in the same status field in the header. This bit seems something of an
  18750. anachronism, but you should nevertheless set it before returning from the
  18751. interrupt routine. The remainder of the interrupt routine restores the
  18752. caller's CPU and stack context.
  18753. After loading your driver, DOS immediately calls the driver initialization
  18754. routine, command code 0 (see init() in Listing 2). You must perform any driver
  18755. setup procedures inside this routine. Note that you are restricted to DOS
  18756. functions 0x01-0x0C and 0x30. Other than these functions, you should avoid
  18757. making DOS calls anywhere in your driver code. In general, since device
  18758. drivers serve as a bridge between applications and the BIOS, you should
  18759. restrict your driver code to BIOS-level calls (or port in/out code if needed)
  18760. and avoid DOS entirely. More to the point, any call your driver makes to DOS
  18761. commits the sin of DOS re-entrancy, since DOS called your driver in the first
  18762. place.
  18763. To inform DOS where to locate the next driver in the chain, the initialization
  18764. routine must return a break address in the request header that specifies the
  18765. end of the driver image in memory. Here I simply label the ENDSEG segment
  18766. (discussed earlier), a segment guaranteed to fall at the end of the driver
  18767. image. init() passes the far address of the labeled segment to DOS as the
  18768. break address. This quick and dirty method, however, means that init() remains
  18769. in memory, where it becomes dead code once DOS has initialized your driver. To
  18770. save memory, most drivers (those written in assembly language, that is) locate
  18771. the break address above the init() code and any data that init() will use only
  18772. once. DOS will then load the next driver over the unneeded parts.
  18773. Although more difficult, this trick can be simulated in C. Since the code
  18774. segment is located first, you can place all code and data in _TEXT, put init()
  18775. in a separate module, and place init.obj last in the link order. You could
  18776. then pass the (void far *) address of the start of init()'s code to DOS as the
  18777. break address. The main obstacle is that most compilers will automatically
  18778. place all data in the _DATA segment. In order to get your data into _TEXT, you
  18779. must define all global data inside an assembly language module and make extern
  18780. references to them in your C modules. Although this scheme works, it
  18781. complicates making changes later. Unless you have particularly lengthy driver
  18782. initialization code, attempting to drop init() is probably more trouble than
  18783. it's worth. To repeat, you're programming a driver in C for C's relative ease
  18784. of use, not primarily to save memory.
  18785.  
  18786.  
  18787. Let The Programmer Beware
  18788.  
  18789.  
  18790. When choosing a name for your device, which goes in the last field of the
  18791. header block, choose a name you won't use for a file. When DOS opens a file,
  18792. it first checks the driver chain for a device driver with the same name. At
  18793. this level, DOS treats devices and files in the same manner. If your file and
  18794. a device name conflict, DOS will open the device instead of your disk file and
  18795. you will get some unexpected results. To illustrate, create a file called lpt1
  18796. with your text editor. Try writing some text to it using the DOS TYPE or COPY
  18797. commands. The data will go to the line printer instead of the disk file. You
  18798. won't be able to delete the file from the DOS command line either.
  18799. It's difficult to incorporate many of the standard C library functions in
  18800. device driver code. You must be absolutely sure you know what they do, since
  18801. some depend on initialized data in other modules or may take actions that in
  18802. some way compromise your driver code. Neither should you use standard library
  18803. functions that call malloc(), because the driver has no heap. You should write
  18804. your own versions of most standard library functions, unless you use only
  18805. simple, compact ones like strcpy().
  18806. Most compilers put uninitialized global data in a special segment that the
  18807. startup code initializes to zeros when a program executes. Many programmers
  18808. write C code that relies on this behavior, but it's an assumption you can't
  18809. make in a device driver written in C. Make sure all data is initialized in
  18810. some way before you use it, the same as you would do when using automatic
  18811. (stack-based) variables. Also, make sure you instruct the compiler to enforce
  18812. byte-packing of structures. Borland enforces byte-packing by default, but I
  18813. believe the Microsoft compiler uses word-packing unless instructed not to.
  18814. Stack probing is a useful adjunct to program debugging, but it is difficult to
  18815. implement a method in a driver that works with a variety of compilers. Though
  18816. I have ignored stack probing in my driver code listings, you may find a way to
  18817. implement it. If your compiler inserts stack probes by default, make sure you
  18818. turn them off.
  18819.  
  18820.  
  18821. State Machines
  18822.  
  18823.  
  18824. State machines are useful in the control of any rule-based system, and their
  18825. application to device control is a prime example. Briefly, you can apply state
  18826. machines to any system you can define in terms of a finite number of states
  18827. (or "islands") with a definite set of rules for traveling or navigating
  18828. between them. The state machine uses initialized data in tables (a state
  18829. table) to organize these states and rules into a structure that reflects the
  18830. actual operation of the system.
  18831. The state machine knows only the current state of the system. Given a command
  18832. or event, the state machine refers to the state table to determine whether
  18833. that event is valid for that state. The event determines the state to which
  18834. the system will travel next. The state table also contains a set of procedures
  18835. (functions) that tell the system how to effect the transition to the next
  18836. state. When the machine reaches the next state, it waits until it receives
  18837. another event or command, then repeats the cycle.
  18838.  
  18839.  
  18840. The State Table In C
  18841.  
  18842.  
  18843.  
  18844. To illustrate the basic concepts of device control using a state machine, I
  18845. developed a simple example that controls a hypothetical tape backup unit. To
  18846. build the state table, start with a list of the available device controller
  18847. commands, as in the simplified list in Figure 1. These commands form the basis
  18848. of a list of valid events, or user commands (see Listing 4, tape.h). You may
  18849. also need to define other user commands, formed from combinations of the basic
  18850. device commands.
  18851. Next, define the various states in which your device will exist at any one
  18852. time. For my tape backup unit, I defined states such as READY, PLAY, and
  18853. RECORD (see Listing 4). Then, given a set of well-defined states, decide which
  18854. events will be valid for each state. These valid events determine the subset
  18855. of states to which you can move, given your current position. For instance, if
  18856. the tape unit is currently in a READY state, meaning you just inserted a
  18857. cassette, you can move directly to any other state. If you're currently in the
  18858. REWIND state, however, you can only stop rewinding and return to a READY
  18859. state.
  18860. When you have defined all your states and the valid connections between them,
  18861. you should end up with something like the table in Table 1, the State
  18862. Transition Table. Along the way you may want to draw some simple diagrams.
  18863. Such diagrams quickly become complex when the number of valid connections
  18864. between states (represented by lines with arrowheads) averages more than two
  18865. or three. You may also want to include a list of the functions needed to
  18866. effect the transition from one state to the next, as I've done in Table 1.
  18867. You can translate the State Transition Table almost directly into a single
  18868. array of structures. Although easy to understand, such an array will mean some
  18869. degree of data repetition. I have instead used a series of cascading or
  18870. multi-level tables connected by pointers, resulting in an efficient use of
  18871. memory as well as reduced coding effort. Each valid state in the system uses
  18872. an array of S_TABs, the basic data element, to create an event table (Listing
  18873. 4). The last member of an event table must be the macro END, which indicates
  18874. the end of the table. An event table defines the valid events for each state,
  18875. the next state to move to, as well as a pointer to the list of functions to
  18876. effect the transition. All the event tables are connected at the top by an
  18877. array of pointers (the state table). Note that the current state of the system
  18878. serves as an index into this array. The order of states in the array should
  18879. correspond to the order in which you initially defined the states, as in
  18880. Listing 4.
  18881. Listing 5 contains the state table. One of the most difficult tasks is finding
  18882. descriptive names for all the tables, especially the lists of pointers to
  18883. functions. At first this may seem overly complex, but working with initialized
  18884. data in tables significantly reduces your coding effort. Without them, your
  18885. code would be littered with logical branching statements.
  18886.  
  18887.  
  18888. The IOCTL Interface
  18889.  
  18890.  
  18891. DOS device drivers make available at least two command code routines to
  18892. control the driver itself. Applications use these IOCTL functions to pass
  18893. control information directly to the driver to control the driver's other I/O
  18894. functions. An IOCTL call does not necessarily result in any input/output
  18895. interaction with the physical device. The control information's format is
  18896. unspecified and is known only to the driver and the application program. DOS
  18897. takes no part except to provide a standard interface for using the IOCTL
  18898. functions. An application wishing to pass control data to a driver uses DOS
  18899. function 44h, sub-functions 2 and 3, which read and write I/O control data,
  18900. respectively.
  18901. DOS passes requests for functions 44h, subfunctions 2 and 3 directly to the
  18902. device driver once you have obtained a device handle using the DOS open
  18903. file/device function, 3Dh. Calls to the DOS IOCTL functions end up in command
  18904. code routines 3 and 12 inside the device driver, ioctl_read() and
  18905. ioctl_write(), respectively ( Listing 2). A developer can use these functions
  18906. for any purpose since nothing is specified other than the calling protocol.
  18907. As you can see in Listing 5, I have used the DOS IOCTL interface as a direct
  18908. channel to control the hypothetical tape drive. The driver uses only command
  18909. routines 0, 3, and 12. Most of the unused routines deal with more "normal"
  18910. device I/O, such as servicing DOS read/write requests. A real tape unit must
  18911. protect itself from such actions, which could easily corrupt data already
  18912. stored on the tape. Without this protection, anyone could write to the tape
  18913. unit using the DOS TYPE command with command-line redirection. However, since
  18914. command routine 8 (device_write() in Listing 2) returns 0 on every call, DOS
  18915. thinks it's writing to a real device, even though nothing is actually done.
  18916. Note also that command routines 3 and 12 both call tape_io() in Listing 5. In
  18917. the interest of uniformity, I combined these two functions, since the only
  18918. real difference between them is semantic. You can therefore use either
  18919. subfunction 2 or 3 of DOS 44h to make IOCTL calls to the tape unit.
  18920.  
  18921.  
  18922. Putting It All Together
  18923.  
  18924.  
  18925. Function tape_io() serves as the entry point into the state machine driver.
  18926. tape_io() receives a far pointer to the command IOCTL string. The pointer is
  18927. passed in the request header's transfer buffer (Listing 3). Since DOS does not
  18928. specify the format of the command information, I defined it as a structure of
  18929. type CMDARG (Listing 4). This structure serves as the state machine's
  18930. primitive memory. It contains the current state of the system, the command for
  18931. the tape unit to execute, and the return status of the command and/or the tape
  18932. unit hardware.
  18933. tape_io() first points to the correct event table, using the CMDARG argument
  18934. as an index into the *s_table[] array. tape_io() then enters a loop,
  18935. attempting to match a valid event for the current state with the command code
  18936. passed in CMDARG. If the function can find no match, it returns immediately
  18937. with an error code. Otherwise,tape_io() updates CMDARG's current state with
  18938. the next valid state from the event table, then immediately executes the
  18939. functions associated with the state transition. The array of function pointers
  18940. doesn't arbitrarily limit the number of functions needed to effect the
  18941. transition, nor does it require null padding to fill unused spaces in the
  18942. array.
  18943. Commands sent to the state machine driver, tape_io(), originate from a
  18944. separate controller program that issues DOS IOCTL calls. The code in ctl.c
  18945. (Listing 6) acts as the tape unit's user interface, something the device
  18946. driver controller code need not be concerned with.
  18947. To use the DOS IOCTL functions, you must first obtain a device handle using
  18948. open () (Listing 6). If open () reports an error, the device driver wasn't
  18949. installed. DOS next determines if the device driver is functioning correctly,
  18950. using function _dos_dev_info(). Two possibilities exist here, one that the
  18951. device name is actually a disk file, or that the "IOCTL" bit (0x4000) in the
  18952. driver's header block was not set. In the latter case, DOS will not complete
  18953. an IOCTL function call because it assumes the device driver doesn't have an
  18954. IOCTL interface. The program uses functions _dos_ioctl_read() and
  18955. _dos_ioctl_write() to actually communicate with the tape unit. As outlined
  18956. earlier, using either function accomplishes the same result, since both end up
  18957. in the state machine driver function, tape_io().
  18958.  
  18959.  
  18960. Alternatives To IOCTL
  18961.  
  18962.  
  18963. Several alternatives to using the DOS IOCTL functions exist. Since DOS serves
  18964. only as an intermediary, you could dispense with the overhead of a DOS call
  18965. entirely and set up the driver as a combination standard device driver and
  18966. TSR, like the Expanded Memory Manager. When DOS initializes your driver
  18967. (command code 0), you would set up a software interrupt to point to the device
  18968. controller code. Application programs would access the driver through the
  18969. interrupt, using much the same command interface as described earlier. You're
  18970. then free to make DOS calls from inside the controller code, since DOS is no
  18971. longer involved up front.
  18972. Continuing along these lines, you could do away with the device driver
  18973. altogether and put the entire controller code inside a TSR. Since the tape
  18974. unit driver doesn't have to service normal I/O requests via DOS, as device
  18975. drivers usually do, you're free to do this.
  18976. However, using a TSR involves a varying amount of risk since DOS never fully
  18977. supported them. Using a state machine in a device driver, on the other hand,
  18978. gives the device driver full support of DOS. By using standard DOS function
  18979. calls, you also avoid potential conflicts over the use of software interrupts.
  18980.  
  18981.  
  18982. Rolling Your Own Driver
  18983.  
  18984.  
  18985. I have presented the driver code in Listing 1 and Listing 2 as a sort of
  18986. template, which you can use to get a head start past the coding basics. The
  18987. template code allows you to concentrate on writing the actual device
  18988. controller code in the language you probably know best. However, even with
  18989. solid support behind you, you can still get stuck when testing and debugging
  18990. your driver.
  18991. Make sure you keep all device controller code in a separate module, as I did
  18992. in Listing 5, tape.c. You can easily convert the module into a transient .EXE
  18993. test bed, using compile-time #defines. Leave the test bed code in place,
  18994. inactivated, when you produce the finished driver. When you make changes and
  18995. retest, simply unpack the test bed code.
  18996. Testing your code as a normal .EXE program means you can easily use a software
  18997. debugger. Your test code should include a means to load a request header with
  18998. appropriate data, exactly as DOS would, and make calls to the exec_command()
  18999. routine. You can then examine the data and status codes returned in the
  19000. request header when the call completes. Making actual calls to the strategy
  19001. and interrupt routines (Listing 1), as DOS does, is probably unnecessary since
  19002. this code is fully debugged.
  19003. A driver still needs to be tested when linked into the DOS driver chain. To
  19004. prepare for this, create a bootable floppy complete with a dummy config.sys
  19005. that includes a command to load your test driver from the hard disk. Also
  19006. include a dummy autoexec.bat that simply calls your normal autoexec.bat file
  19007. on the hard disk.
  19008. You'll probably find a software-only debugger difficult to use when testing a
  19009. driver in place, although I have never tried it. You can set breakpoints
  19010. within the body of the driver much as you normally would. However, most
  19011. debuggers make DOS calls too. When they do so, they re-enter DOS, corrupting
  19012. the DOS stack and leading directly to a system crash. Instead of using a
  19013. debugger, you should rely as much as possible on testing the code thoroughly
  19014. as an .EXE transient, as outlined above. To test the driver in place, you may
  19015. find a "non-intrusive monitor" to be of some help (see reference [5]). You can
  19016. then monitor the values of selected variables inside the driver during
  19017. execution of a normal test program running in the foreground.
  19018.  
  19019.  
  19020. Some Final Thoughts
  19021.  
  19022.  
  19023. If you've tinkered with device drivers before and gotten stuck, this article
  19024. may give you new impetus to try again. Even if you know your way around DOS
  19025. drivers, it always helps to have a fresh slant on them, if only to affirm that
  19026. your own way of doing it is better.
  19027. This article has also shown that a state machine approach to writing device
  19028. control code can provide considerable benefits. Its table-driven strategy
  19029. forms a natural, built-in Application Program Interface (API) for device
  19030. control from an application program. Using the API, anyone can build a desired
  19031. user interface to the tape unit. You're also able to modify the user interface
  19032. at any time without touching the device driver.
  19033. This arrangement is very flexible, since you can locate the user interface
  19034. code in any situation. You could place such code in a normal .EXE application
  19035. that includes tape backups as an option. Another possibility would be a TSR
  19036. that uses the tape driver to make background saves of specified files when the
  19037. TSR detects that the files have been modified.
  19038. References
  19039. Duncan, Ray. Advanced MS-DOS Programming, 2nd Ed.. Microsoft Press, Redmond,
  19040. WA.
  19041. Fischer, Paul. "State Machines in C," The C Users Journal, December 1990, pp.
  19042. 119-122.
  19043. Johnson, Marcus. "Writing MS-DOS Device Drivers," The C Users Journal,
  19044. December 1990, pp. 41-57.
  19045. Lai, Robert. S. and The Waite Group. Writing MS-DOS Device Drivers.
  19046. Addison-Wesley.
  19047. Naleszkiewicz, John R. "A Non-Intrusive TSR Monitor," TECH Specialist, June
  19048. 1991, pp. 32-40.
  19049. Figure 1 TAPE Device Controller Functions
  19050. 1. Query device status
  19051.  
  19052. 2. Read data block
  19053. 3. Write data block
  19054. 4. Enable fast forward
  19055. 5. Enable rewind
  19056. 6. Stop
  19057. 7. Initialize device
  19058. 8. Eject
  19059.  
  19060. Table 1 State Transition Table
  19061. Current State Valid Commands Next State Functions
  19062. --------------------------------------------------------------
  19063. OFF CMD_POWER_ON ON initialize, status
  19064.  CMD_CHK_STATUS OFF status
  19065. ON CMD_POWER_OFF OFF power off
  19066.  CMD_INSERT READY status
  19067.  CMD_CHK_STATUS ON status
  19068. READY CMD_POWER_OFF OFF eject, power off
  19069.  CMD_EJECT ON eject
  19070.  CMD_RECORD RECORD record
  19071.  CMD_PLAY PLAY play
  19072.  CMD_FFORWARD FFORWARD fforward
  19073.  CMD_REWIND REWIND rewind
  19074.  CMD_CHK_STATUS READY status
  19075. RECORD CMD_STOP READY stop
  19076.  CMD_EJECT ON stop, eject
  19077.  CMD_FFORWARD FFORWARD stop, fforward
  19078.  CMD_PLAY PLAY stop, play
  19079.  CMD_REWIND REWIND stop, rewind
  19080.  RECORD RECORD status
  19081. PLAY CMD_STOP READY stop
  19082.  CMD_EJECT ON stop, eject
  19083.  CMD_FFORWARD FFORWARD stop, fforward
  19084.  CMD_REWIND REWIND stop, rewind
  19085.  CMD_RECORD RECORD stop, record
  19086.  CMD_CHK_STATUS PLAY status
  19087. FFORWARD CMD_STOP READY stop
  19088.  CMD_EJECT ON stop, eject
  19089.  CMD_PLAY PLAY stop, play
  19090.  CMD_REWIND REWIND stop, rewind
  19091.  CMD_RECORD RECORD stop, record
  19092.  CMD_CHK_STATUS FFORWARD status
  19093. REWIND CMD_STOP READY stop
  19094.  CMD_EJECT ON stop, eject
  19095.  CMD_PLAY PLAY stop, play
  19096.  CMD_RECORD RECORD stop, record
  19097.  CMD_FFORWARD FFORWARD stop, fforward
  19098.  CMD_CHK_STATUS REWIND status
  19099.  
  19100. Listing 1 (start.asm) Device Driver Startup Module for C
  19101. ; -----------------------------------------------------
  19102. ; This module must appear first in the link order.
  19103. ;
  19104. ; Coded for TASM 2.0
  19105. ;
  19106. ; Source code Copyright (c) 1991 T.W. Nelson.
  19107. ; May be used only with appropriate
  19108. ; acknowledgement of copyright.
  19109. ; -----------------------------------------------------
  19110. ;
  19111.  
  19112. ; define segment order first ....
  19113. ;
  19114.  
  19115. _TEXT segment byte public 'CODE'
  19116. _TEXT ends
  19117. -DATA segment word public 'DATA'
  19118. -DATA ends
  19119. -BSS segment word public 'BSS'
  19120. -BSS ends
  19121. ENDSEG segment word public 'ENDSEG'
  19122. ENDSEG ends
  19123.  
  19124. ; Establish TINY model segment group ....
  19125. ;
  19126. DGROUP group _TEXT,_DATA,_BSS,ENDSEG
  19127. ASSUME cs:DGROUP,ds:DGROUP,es:NOTHING,ss:NOTHING
  19128.  
  19129. STACKSIZ equ 256 ;stack size, words
  19130.  
  19131. _TEXT segment
  19132.  
  19133. org 0 ;all driver headers start at 0
  19134.  
  19135. ; driver header data .......
  19136. ;
  19137.  
  19138. db 4 dup(Offh) ;points to ground (NULL)
  19139. dw 8000h + 4000h ;driver attributes: char device
  19140. ;...and ioctl read/write
  19141. dw _dev_strategy ;offset to strategy routine
  19142. dw _dev_interrupt ;offset to interrupt routine
  19143. db 'TAPEXXXX' ;device name = 8 bytes
  19144.  
  19145. rhdr_seg dw ? ;request header segment
  19146. rhdr_off dw ? ;request header offset
  19147. caller_ss dw ? ;for stack setup
  19148. caller_sp dw ?
  19149.  
  19150. ; Function appears in module 'exec.c' and must be
  19151. ; defined as:
  19152. ;
  19153. ; void exec_command( REQHDR far *rhdr )
  19154. ;
  19155. extrn_exec_command:near
  19156.  
  19157. ; First part of two-step driver call from DOS. This
  19158. ; only saves a pointer to the request header used in
  19159. ; the interrupt routine ......
  19160. ;
  19161. _dev_strategy proc far ;always far DOS call
  19162. mov cs:rhdr_seg,es
  19163. mov cs:rhdr_off,bx
  19164. ret
  19165. _dev_strategy endp
  19166.  
  19167. ; Second (workhorse) part of 2-step driver call.
  19168. ; Although called an 'interrupt' function, this is
  19169. ; not a true interrupt handler .......
  19170. ;
  19171.  
  19172. _dev_interrupt proc far ;always far DOS call
  19173.  
  19174. ;----- Save caller's CPU state and stack context ....
  19175. push ax
  19176. push bx
  19177. push cx
  19178. push dx
  19179. push di
  19180. push si
  19181. push bp
  19182. push ds
  19183. push es
  19184. pushf
  19185. mov cs:caller_ss,ss
  19186. mov cs:caller_sp,sp
  19187.  
  19188. ;----- Setup segment addressability and driver
  19189. ; stack frame ............
  19190. mov ax,cs ;local data seg
  19191. cli
  19192. mov ds,ax
  19193. mov es,ax
  19194. mov ss,ax
  19195. mov sp,offset cs:stack_top
  19196. mov bp,sp ;set C stack frame
  19197. sti
  19198.  
  19199. ;----- Pass pointer to req hdr and execute command....
  19200. mov ax,cs:rhdr_seg
  19201. push ax
  19202. mov ax,cs:rhdr_off
  19203. push ax
  19204. call _exec_command
  19205. pop ax
  19206. pop ax
  19207.  
  19208. ;----- restore CPU state ..........
  19209. cli
  19210. mov ss,cs:caller_ss
  19211. mov sp,cs:caller_sp
  19212. sti
  19213. popf
  19214. pop es
  19215. pop ds
  19216. pop bp
  19217. pop si
  19218. pop di
  19219. pop dx
  19220. pop cx
  19221. pop bx
  19222. pop ax
  19223. ret
  19224. _dev_interrupt endp
  19225.  
  19226. _TEXT ends
  19227.  
  19228. ; ------------------------------------------------------
  19229.  
  19230. _DATA segment
  19231.  
  19232.  
  19233. dw STACKSIZ dup(?) ;driver's stack
  19234. stack_top dw ?
  19235.  
  19236. _DATA ends
  19237.  
  19238. ; ------------------------------------------------------
  19239.  
  19240. ENDSEG segment
  19241.  
  19242. __TheEnd label byte ;location of cs:__TheEnd
  19243. ;marks break address for DOS
  19244. public __TheEnd
  19245.  
  19246. ENDSEG ends
  19247.  
  19248. END
  19249.  
  19250. ; ------ End of File -----------------------------------
  19251.  
  19252.  
  19253. Listing 2 (exec.c) Execute Driver Command
  19254. /* ----------------------------------------------------
  19255. * Author: T.W. Nelson
  19256. * Compile options:
  19257. * Version: 1.00
  19258. * Date: 05-0ct-1991
  19259. * Notes:
  19260. *
  19261. * Source code Copyright (c) 1991 T.W. Nelson.
  19262. * May be used only with appropriate
  19263. * acknowledgement of copyright.
  19264. * -------------------------------------------------- */
  19265.  
  19266. #include <dos.h>
  19267. #include "driver.h"
  19268.  
  19269. extern unsigned init(void), media_check(void),
  19270. build_bpb(void), ioctl_read(void),
  19271. device_read(void), nd_read(void),
  19272. input_status(void), input_flush(void),
  19273. device_write(void), verify_write(void),
  19274. output_status(void), output_flush(void),
  19275. ioctl_write(void), device_open(void),
  19276. device_close(void), rem_media(void),
  19277. output_busy(void), generic_ioctl(void),
  19278. get_logdev(void), set_logdev(void),
  19279. bad_command(void);
  19280.  
  19281. static unsigned (*Dispatch[])(void) = {
  19282. init, //0 = initialize driver
  19283. media_check, //1
  19284. build_bpb, //2 = build BIOS param block
  19285. ioctl_read, //3 = read io control data
  19286. device_read, //4
  19287. nd_read, //5 = non-destructive read
  19288. input_status, //6
  19289. input_flush, //7 = flush input buffers
  19290. device_write, //8
  19291.  
  19292. verify_write, //9 = write with verify
  19293. output_status, //10
  19294. output_flush, //11 = flush output buffers
  19295. ioctl_write, //12 = write io control data
  19296. device_open, //13 (DOS 3+)
  19297. device_close, //14 (DOS 3+)
  19298. rem_media, //15 = removable media (3+)
  19299. output_busy, //16 = output until busy (3+)
  19300. bad_command, //17 = not used
  19301. bad_command, //18 = not used
  19302. generic_ioctl, //19 = generic io control (3.2+)
  19303. bad_command, //20 = not used
  19304. bad_command, //21 = not used
  19305. bad_command, //22 = not used
  19306. get_logdev, //23 = get logical device (3.2+)
  19307. set_logdev, //24 = set logical device (3.2+)
  19308. };
  19309.  
  19310. #define NUM_CMDS (sizeof(Dispatch)/sizeof(void *))
  19311.  
  19312. REQHDR far *Rh = (REQHDR far *) 0;
  19313. unsigned int errno = 0; //for std library code
  19314. extern unsigned tape_io(unsigned ofs, unsigned seg);
  19315.  
  19316. static char signon[] =
  19317. "Demo TAPE device driver, V1.00\r\n\n";
  19318.  
  19319. void exec_command( REQHDR far *rhdr )
  19320. {
  19321. /* Call the command-code routine from the dispatch
  19322. * table indexed by the command code. Access to
  19323. * caller's request header is made available thru
  19324. * global pointer 'Rh'. 'DONE' bit is always set
  19325. * on exit.
  19326. */
  19327.  
  19328. Rh = rhdr; //assign to global copy
  19329. Rh->status = 0; //clear status word
  19330.  
  19331. Rh->status = ( (Rh->cmd >= NUM_CMDS) ?
  19332. bad_command() :
  19333. (*Dispatch[ Rh->cmd ])() );
  19334. Rh->status = IM_DONE; //set 'done' bit
  19335. }
  19336.  
  19337. /*----------------------------------------------------
  19338. * Command-code routines. Called indirectly by the
  19339. * interrupt routine thru the dispatch table. All
  19340. * routines should return the appropriate status code
  19341. * to be assigned to the request header on return to
  19342. * exec_command(). Return will be 0 if no error,
  19343. * or (IS_ERROR + error_code) (see driver.h) if an
  19344. * error was detected.
  19345. *----------------------------------------------------*/
  19346.  
  19347. unsigned init( void ) /* 0 */
  19348. {
  19349. /* Driver initialization function. Called once
  19350. * by DOS on boot up when CONFIG.SYS is read.
  19351.  
  19352. * Only DOS functions 01h-OCh and 30h can be called
  19353. * here.
  19354. */
  19355.  
  19356. extern char _TheEnd; //marks end of driver
  19357. void putstr( const char *str ); //below
  19358.  
  19359. putstr( signon);
  19360.  
  19361. /* Other initialization code goes here ......
  19362. * If needed, a pointer to the command line after
  19363. * 'DEVICE=' in CONFIG.SYS is available starting
  19364. * at Rh->xfer_cnt (or, Rh + 18).
  19365. */
  19366.  
  19367. // Return break address to DOS .......
  19368. Rh->xfer_seg = FP_SEG( (void far *) &_TheEnd );
  19369. Rh->xfer_ofs = FP_OFF( (void far *) &_TheEnd );
  19370.  
  19371. return 0;
  19372. }
  19373.  
  19374. unsigned media_check( void ) /* 1 */
  19375. {
  19376. /* Block devices only. Function determines
  19377. * whether or not a floppy disk was changed,
  19378. * allowing DOS to bypass re-reading the FAT.
  19379. */
  19380. return 0; }
  19381.  
  19382. unsigned build_bpb(void) /* 2 */
  19383. {
  19384. /* Block devices only. Builds a table of disk
  19385. * parameters when media_check() returns the
  19386. * disk-change code.
  19387. */
  19388. return 0; }
  19389.  
  19390. unsigned ioctl_read(void) /* 3 */
  19391. {
  19392. /* Character and block devices. Allows device
  19393. * driver to pass data directly to application.
  19394. * Called by DOS int21h/44h subfunction 2. The
  19395. * IOCTL_RW bit (14) must be set in device block's
  19396. * attribute word.
  19397. */
  19398. return tape_io(Rh->xfer_ofs, Rh->xfer_seg);
  19399. }
  19400.  
  19401. unsigned device_read(void) /* 4 */
  19402. {
  19403. /* Character and block devices. Read data from
  19404. * the device into a specified buffer. Returns
  19405. * #bytes or sectors transfered.
  19406. */
  19407. return 0; }
  19408.  
  19409. unsigned nd_read(void) /* 5 */
  19410. {
  19411.  
  19412. /* Character devices only. Lets caller peek at
  19413. * next byte in device's buffer w/o removing it.
  19414. */
  19415. return 0; }
  19416.  
  19417. unsigned input_status(void) /* 6 */
  19418. {
  19419. /* Character devices only. Returns current input
  19420. * status for device. Sets IM_BUSY bit if input
  19421. * request cannot proceed immediately (characters
  19422. * are waiting to be read from the input buffers).
  19423. */
  19424. return 0; }
  19425.  
  19426. unsigned input_flush(void) /* 7 */
  19427. {
  19428. /* Character devices only. Function discards any
  19429. * data pending in device's input buffers.
  19430. */
  19431. return 0; }
  19432.  
  19433. unsigned device_write(void) /* 8 */
  19434. {
  19435. /* Character and block devices. Writes data from
  19436. * the specified buffer to a device. Returns #
  19437. * bytes or sectors written.
  19438. */
  19439. return 0; }
  19440.  
  19441. unsigned verify_write(void) /* 9 */
  19442. {
  19443. /* Character and block devices. Same as
  19444. * device_write(), but performs a read-after-
  19445. * write verification.
  19446. */
  19447. return 0; }
  19448.  
  19449. unsigned output_status(void) /* 10 */
  19450. {
  19451. /* Character devices only. Returns the current
  19452. * output status for the device. Sets IM_BUSY bit
  19453. * if write request cannot proceed immediately.
  19454. */
  19455. return 0; }
  19456.  
  19457. unsigned output_flush(void) /* 11 */
  19458. {
  19459. /* Character devices only. Function discards any
  19460. * data in output buffers and any pending output
  19461. * requests.
  19462. */
  19463. return 0; }
  19464.  
  19465. unsigned ioctl_write(void) /* 12 */
  19466. {
  19467. /* Character and block devices. Allows application
  19468. * to pass control info directly to driver. Called
  19469. * by DOS int21h/44h subfunction 3. The IOCTL_RW
  19470. * bit (14) must be set in device block's attribute
  19471.  
  19472. * word.
  19473. */
  19474. return tape_io(Rh->xfer_ofs, Rh->xfer_seg);
  19475. }
  19476.  
  19477. unsigned device_open(void) /* 13 */
  19478. {
  19479. /* Character and block devices, DOS 3+. Manages
  19480. * reference count of #open files for block devices,
  19481. * or device initialization for character devices.
  19482. * Called only if bit 11 (OCR_MEDIA) is set in
  19483. * attribute word in header block.
  19484. */
  19485. return 0; }
  19486.  
  19487. unsigned device_close(void) /* 14 */
  19488. {
  19489. /* Character and block devices, DOS 3+. The
  19490. * opposite of command-code 13, device_open().
  19491. */
  19492. return 0; }
  19493.  
  19494. unsigned rem_media(void) /* 15 */
  19495. { /* Block devices only. Sets IM_BUSY if media is
  19496. * non-removable. Called only if bit 11
  19497. * (OCR_MEDIA) is set in attribute word in header
  19498. * block.
  19499. */
  19500. return 0; }
  19501.  
  19502. unsigned output_busy(void) /* 16 */
  19503. {
  19504. /* Character devices only, DOS 3+. Transfers data
  19505. * to a device from specified buffer until device
  19506. * signals busy. Called only if attribute bit 13
  19507. * (OUTPUT_BUSY) is set.
  19508. */
  19509. return 0; }
  19510.  
  19511. unsigned generic_ioctl(void) /* 19 */
  19512. {
  19513. /* Character and block devices, DOS 3.2+. This
  19514. * function is generally the same as command-codes
  19515. * 3 and 12, with a different calling protocol. Bit
  19516. * 6 (GEN_IOCTL) must be set in attribute word.
  19517. */
  19518. return 0; }
  19519.  
  19520. unsigned get_logdev(void) /* 23 */
  19521. {
  19522. /* Block devices only, DOS 3.2+. Function returns
  19523. * number of logical devices (drive letters)
  19524. * assigned to a physical device. Bit 6
  19525. * (GEN_IOCTL) must be set in attribute word.
  19526. */
  19527. return 0; }
  19528.  
  19529. unsigned set_logdev(void) /* 24 */
  19530. {
  19531.  
  19532. /* Block devices only, DOS 3.2+. Function sets
  19533. * number of logical devices assigned to a
  19534. * physical device. Bit 6 (GEN_IOCTL) must be set
  19535. * in attribute word.
  19536. */
  19537. return 0; }
  19538.  
  19539.  
  19540. unsigned bad_command( void )
  19541. {
  19542. return IS_ERROR + INV_COMMAND;
  19543. }
  19544.  
  19545. void putstr( const char *str )
  19546. {
  19547. /* Utility function to output an asciiz-format
  19548. * string to monitor using BIOS-level output....
  19549. * Doesn't add an auto-CR; use '\r' in your
  19550. * string. Coding putstr() in C adds 200-300 more
  19551. * bytes (from int86()) to the TAPE.SYS image in
  19552. * memory, relative to an asm version.
  19553. */
  19554.  
  19555. union REGS regs;
  19556.  
  19557. while( *str) {
  19558. regs.h.al = (char) *str++;
  19559. regs.h.ah = 0x0e; //BIOS write TTY
  19560. regs.x.bx = 0; //page 0
  19561. int86( 0x10, ®s, ®s );
  19562. }
  19563. }
  19564.  
  19565. /* ------ End of File --------------------*/
  19566.  
  19567.  
  19568. Listing 3 (driver._H) MS-DOS Device Driver #defines and Definitions
  19569. #ifndef_DRIVER_H
  19570. #define_DRIVER_H
  19571.  
  19572. /* Format of generic request header block. Depending
  19573. * on the command code, data specific to the command is
  19574. * carried in data[] buffer ......... */
  19575. typedef struct request_block {
  19576. unsigned char length; //length of REQHDR
  19577. unsigned char unit; //unit # (block only)
  19578. unsigned char cmd; //command code
  19579. unsigned int status; //return status
  19580. unsigned char reserved[8];
  19581. unsigned char mtype; //media type
  19582. unsigned int xfer_ofs; //xfer buffer offset
  19583. unsigned int xfer_seg; // " " segment
  19584. unsigned int xfer_cnt; //bytes to xfer
  19585. unsigned char data[12]; //other command data
  19586. } REQHDR;
  19587.  
  19588. /* bit settings for upper byte of return
  19589. * status word in REQHDR .... */
  19590. #define IS_ERROR 0x8000
  19591.  
  19592. #define IM-BUSY 0x0200
  19593. #define IM_DONE 0x0100
  19594.  
  19595. /* values returned in lower byte of REQHDR status
  19596. * word if ERROR bit (bit 15) is set ..... */
  19597. #define WRITE_PROTECT 0x0000
  19598. #define INVALID_UNIT 0x0001
  19599. #define NOT_READY 0x0002
  19600. #define INV_COMMAND 0x0003
  19601. #define CRC_ERROR 0x0004
  19602. #define BAD_REQ_LENGTH 0x0005
  19603. #define SEEK_ERROR 0x0006
  19604. #define UNKNOWN_MEDIA 0x0007
  19605. #define INV_SECTOR 0x0008
  19606. #define OUT_OF_PAPER 0x0009
  19607. #define WRITE_FAULT 0x000A
  19608. #define READ_FAULT 0x000B
  19609. #define GENERAL_FAILURE 0x000C
  19610. #define INV_DISK_CHANGE 0x000F //DOS 3+ only
  19611.  
  19612. /* Bit settings for attribute word ........ */
  19613. #define IS_NUL 0x0004 //current NUL device
  19614. #define IS_CLOCK 0x0008 //current CLOCK device
  19615. #define GEN_IOCTL 0x0040 //generic ioctl: DOS 3.2+
  19616. #define OCR_MEDIA 0x0800 //open-close-remove media
  19617. #define IOCTL_RW 0x4000 //ioctl r/w supported (2+)
  19618.  
  19619. /* Attribute bits for character devices only ...... */
  19620. #define IS_STDIN 0x0001 //std input device
  19621. #define IS_STDOUT 0x0002 //std output device
  19622. #define USE_INT29H 0x0010 //CON device uses int 29h
  19623. /define OUTPUT_BUSY 0x2000 //uses output until busy
  19624. /define IS_CHAR 0x8000 //is character device
  19625.  
  19626. /* Attribute bits for block devices only .... */
  19627. #define BIG_SECTOR 0x0002 //32-bit sectors (DOS 4)
  19628. #define USE_BPB 0x2000 //use BPB for media info
  19629.  
  19630. #endif //__DRIVER_H
  19631.  
  19632. /* ------ End of File ----------------------- */
  19633.  
  19634.  
  19635. Listing 4 (tape.h) State Table Macros and Structure Definitions for TAPE
  19636. Device
  19637. /* Device states .... */
  19638. enum device_states {
  19639. OFF, //0
  19640. ON, //1
  19641. READY, //2
  19642. RECORD, //3
  19643. PLAY, //4
  19644. FFORWARD, //5
  19645. REWIND, //6
  19646. MAX_STATE //7
  19647. };
  19648.  
  19649. /* User commands (events)..... */
  19650. enum user_cmds {
  19651. CMD_POWER_ON,
  19652.  
  19653. CMD_POWER_OFF,
  19654. CMD_INSERT,
  19655. CMD_EJECT,
  19656. CMD_RECORD,
  19657. CMD_PLAY,
  19658. CMD_FFORWARD,
  19659. CMD_REWIND,
  19660. CMD_CHK_STATUS,
  19661. CMD_STOP
  19662. };
  19663.  
  19664. #define END -1
  19665. #define OK 0
  19666. #define NOTOK -1
  19667.  
  19668. typedef struct command_arg {
  19669. int c_state; //current state
  19670. int cmd; //driver command
  19671. int status; //device status
  19672. } CMDARG;
  19673.  
  19674. typedef struct state_table {
  19675. int event;
  19676. int n_state;
  19677. int (**procs)(CMDARG far *arg); //-> to array of
  19678. //....pointers to functions
  19679. } S_TAB;
  19680.  
  19681. /* ----- End of File ------------------------*/
  19682.  
  19683.  
  19684. Listing 5 (tape.c) State Machine Constructs for TAPE Device
  19685. /* -----------------------------------------------------
  19686. * Author: T.W. Nelson
  19687. * Compile options:
  19688. * Version: 1.00
  19689. * Date: 07-Oct-1991
  19690. * Notes:
  19691. *
  19692. * Source code Copyright (c) 1991 T.W. Nelson.
  19693. * May be used only with appropriate
  19694. * acknowledgement of copyright.
  19695. * -------------------------------------------------- */
  19696.  
  19697. #include "driver.h"
  19698. #include "tape.h"
  19699.  
  19700. extern void putstr( const char *str );
  19701.  
  19702. extern int initialize(), status(), eject(), record(),
  19703. play(), fforward(), rewind(), stop(), cstate(),
  19704. shutdown();
  19705.  
  19706. //Arrays of function pointers to be executed
  19707. //in the order listed, to effect transition from
  19708. //the current state to next desired state.....
  19709. //
  19710. int (*f_init[])() = { initialize, cstate, 0 };
  19711.  
  19712.  
  19713. int (*f_stat[])() = { status, cstate, 0 };
  19714.  
  19715. int (*f_off[])() = { shutdown, cstate, 0 };
  19716.  
  19717. int (*f_ejoff[])() = { eject, shutdown, cstate, 0 };
  19718.  
  19719. int (*f_eject[])() = { eject, cstate, 0 };
  19720.  
  19721. int (*f_rec[])() = { record, cstate, 0 };
  19722.  
  19723. int (*f_play[])() = { play, cstate, 0 };
  19724.  
  19725. int (*f_ffwd[])() = { fforward, cstate, 0 };
  19726.  
  19727. int (*f_rwnd[])() = { rewind, cstate, 0 };
  19728.  
  19729. int (*f_stop[])() = { stop, cstate, 0 };
  19730.  
  19731. int (*f_steject[])() = { stop, eject, cstate, 0 };
  19732.  
  19733. int (*f_strec[])() = { stop, record, cstate, 0 };
  19734.  
  19735. int (*f_stplay[])() = { stop, play, cstate, 0 };
  19736.  
  19737. int (*f_stffwd[])() = { stop, fforward, cstate, 0 };
  19738.  
  19739. int (*f_strwnd[])() = { stop, rewind, cstate, 0 };
  19740.  
  19741. //Event (Command) Tables describing valid events for a
  19742. //state, the next state to transition to, as well as
  19743. //the functions to effect the transition......
  19744. //
  19745. S TAB s_off[] = {
  19746. /* command n_state funcs
  19747. --------------------------------- */
  19748. CMD_POWER_ON, ON, f_init,
  19749. CMD_CHK_STATUS, OFF, f_stat,
  19750. END, END, 0, };
  19751.  
  19752. S_TAB s_on[] = {
  19753. /* command n_state funcs
  19754. ------------------------------------ */
  19755. CMD_POWER_OFF, OFF , f_off,
  19756. CMD_INSERT , READY, f_stat,
  19757. CMD_CHK_STATUS, ON, f_stat,
  19758. END , END , 0, };
  19759.  
  19760. S_TAB s_ready[] = {
  19761. /* command n_state funcs
  19762. ------------------------------------ */
  19763. CMD_POWER_OFF , OFF , f_ejoff,
  19764. CMD_EJECT , ON , f_eject,
  19765. CMD_RECORD , RECORD , f_rec,
  19766. CMD_PLAY , PLAY , f_play,
  19767. CMD_FFORWARD , FFORWARD, f_ffwd,
  19768. CMD_REWIND , REWIND , f_rwnd,
  19769. CMD_CHK_STATUS, READY , f_stat,
  19770. END , END , 0, };
  19771.  
  19772.  
  19773. S_TAB s_record[] = {
  19774. /* command n_state funcs
  19775. ------------------------------------ */
  19776. CMD_STOP , READY , f_stop,
  19777. CMD_EJECT , ON , f_steject,
  19778. CMD_FFORWARD , FFORWARD, f_stffwd,
  19779. CMD_PLAY , PLAY , f_stplay,
  19780. CMD_REWIND , REWIND , f_strwnd,
  19781. CMD_CHK_STATUS, RECORD , f_stat,
  19782. END , END , 0, };
  19783.  
  19784. S_TAB s_play[] = {
  19785. /* command n_state funcs
  19786. ------------------------------------ */
  19787. CMD_STOP , READY , f_stop,
  19788. CMD_EJECT , ON , f_steject,
  19789. CMD_FFORWARD , FFORWARD, f_stffwd,
  19790. CMD_REWIND , REWIND , f_strwnd,
  19791. CMD_RECORD , RECORD , f_strec,
  19792. CMD_CHK_STATUS, PLAY , f_stat,
  19793. END , END , 0, };
  19794.  
  19795. S_TAB s_fforward[] = {
  19796. /* command n_state funcs
  19797. ------------------------------------ */
  19798. CMD_STOP , READY , f_stop,
  19799. CMD_EJECT , ON , f_steject,
  19800. CMD_PLAY , PLAY , f_stplay,
  19801. CMD_REWIND , REWIND , f_strwnd,
  19802. CMD_RECORD , RECORD , f_strec,
  19803. CMD_CHK_STATUS, FFORWARD, f_stat,
  19804. END , END , 0, };
  19805.  
  19806. S_TAB s_rewind[] = {
  19807. /* command n_state funcs
  19808. ------------------------------------ */
  19809. CMD_STOP , READY , f_stop,
  19810. CMD_EJECT , ON , f_steject,
  19811. CMD_PLAY , PLAY , f_stplay,
  19812. CMD_RECORD , RECORD , f_strec,
  19813. CMD_FFORWARD , FFORWARD, f_stffwd,
  19814. CMD_CHK_STATUS, REWIND , f_stat,
  19815. END , END , 0, };
  19816.  
  19817. //The actual "state table" that connects all
  19818. //the foregoing initialized data into one. enum
  19819. //'device states' is an index into this array
  19820. //of struct pointers. These MUST be in same order
  19821. //as the enum in 'tape.h' ........
  19822. //
  19823. S_TAB *s_table[] = {
  19824. s_off, s_on, s_ready, s_record, s_play,
  19825. s_fforward, s_rewind,
  19826. };
  19827.  
  19828. unsigned tape_io( CMDARG far *arg )
  19829. {
  19830. /* State Machine Driver. Called by DOS thru driver
  19831. * interrupt routine, command codes 3 & 12.
  19832.  
  19833. */
  19834.  
  19835. int i;
  19836. S_TAB *pcmd; //-> event (cmd) table
  19837. int (**pfun)(CMDARG far *arg); //-> function list
  19838.  
  19839. //point to the correct event table .....
  19840. if( arg->c_state >= MAX_STATE )
  19841. return IS_ERROR-+ INV_COMMAND;
  19842.  
  19843. pcmd = s_table[arg->c_state];
  19844.  
  19845. //find the specified command in the event table...
  19846. for( i = 0; pcmd[i].event != END; i++ )
  19847. if( pcmd[i].event == arg->cmd )
  19848. break;
  19849.  
  19850. //check for invalid command .....
  19851. if( pcmd[i].event == END ) {
  19852. putstr("Invalid command specified\r\n");
  19853. return IS_ERROR + INV_COMMAND;
  19854. }
  19855.  
  19856. //indicate next state for application .......
  19857. arg->c_state - pcmd[i].n_state;
  19858.  
  19859. //execute the function list ........
  19860. pfun = pcmd[i].procs;
  19861.  
  19862. for( i = 0;pfun[i]; i++)
  19863. (*pfun[i])(arg);
  19864.  
  19865. return 0;
  19866. }
  19867.  
  19868. int status(CMDARG far *arg)
  19869. {
  19870. arg->status = OK;
  19871. return 0;
  19872. }
  19873.  
  19874. int initialize(CMDARG far *arg)
  19875. {
  19876. putstr("Device initialized\r\n");
  19877. status(arg);
  19878. return 0;
  19879. }
  19880.  
  19881. int eject(CMDARG far *arg)
  19882. {
  19883. putstr( "Cassette ejected\r\n");
  19884. status(arg);
  19885. return 0;
  19886. }
  19887.  
  19888. int record(CMDARG far *arg)
  19889. {
  19890. putstr( "Recording\r\n");
  19891. status(arg);
  19892.  
  19893. return 0;
  19894. }
  19895.  
  19896. int play(CMDARG far *arg)
  19897. {
  19898. putstr( "Playing\r\n");
  19899. status(arg);
  19900. return 0;
  19901. }
  19902.  
  19903. int fforward(CMDARG far *arg)
  19904. {
  19905. putstr( "Fast forwarding\r\n");
  19906. status(arg);
  19907. return 0;
  19908. }
  19909.  
  19910. int rewind(CMDARG far *arg)
  19911. {
  19912. putstr( "Rewinding\r\n");
  19913. status(arg);
  19914. return 0;
  19915. }
  19916.  
  19917. int stop(CMDARG far *arg)
  19918. {
  19919. putstr( "Stopping\r\n"};
  19920. status(arg);
  19921. return 0;
  19922. }
  19923.  
  19924. int shutdown(CMDARG far *arg)
  19925. {
  19926. putstr( "Powered down\r\n");
  19927. status(arg);
  19928. return 0;
  19929. }
  19930.  
  19931. int cstate(CMDARG far *arg)
  19932. {
  19933. /*Print current state, after transition ....*/
  19934.  
  19935. static char *s_name[] = {
  19936. "OFF", //0
  19937. "ON", //1
  19938. "READY", //2
  19939. "RECORD", //3
  19940. "PLAY", //4
  19941. "FFORWARD", //5
  19942. "REWIND", //6
  19943. };
  19944.  
  19945. putstr( "Current state is");
  19946. putstr( s_name[arg->c_state] );
  19947. putstr( "\r\n" );
  19948. return 0;
  19949.  
  19950. /* ----- End of File ------------------------ */
  19951.  
  19952.  
  19953.  
  19954. Listing 6 (ctl.c) Test IOCTL Program for TAPE Device Driver
  19955. /* ----------------------------------------------------
  19956. * Author: T.W. Nelson
  19957. * Compile options:
  19958. * Version: 1.00
  19959. * Date: 29-Sep-1991
  19960. * Notes:
  19961. * ------------------------------------------------- */
  19962.  
  19963. #include <stdio.h>
  19964. #include <io.h>
  19965. #include <fcntl.h>
  19966. #include <sys\stat.h>
  19967. #include <dos.h>
  19968. #include <bios.h>
  19969. #include "tape.h"
  19970.  
  19971. #define IOCTL_OK Ox4000 //ioctl check bit
  19972. #define IM_A_DEVICE 0x0080 //device check bit
  19973. #define IO_BUF_SIZE_sizeof(CMDARG)
  19974.  
  19975. union REGS regs;
  19976. struct SREGS sregs;
  19977. char Dname[] = "TAPEXXXX";
  19978. int ioHandle;
  19979. CMDARG ioArg;
  19980.  
  19981. char *Menu[] = {
  19982. "",
  19983. "Power ON .............0",
  19984. "Power OFF ............1",
  19985. "Insert Cassette ......2',
  19986. "Eject Cassette .......3",
  19987. "Record ...............4",
  19988. "Play .................5",
  19989. "Fast Forward .........6",
  19990. "Rewind ...............7",
  19991. "Check Status .........8",
  19992. "Stop .................9",
  19993. "Quit .................<Esc>",
  19994. "",
  19995. 0
  19996. );
  19997.  
  19998. extern int_doserrno;
  19999.  
  20000. int _dos_dev_info( int handle,
  20001. size_t *info )
  20002. {
  20003. /* Get information about specified handle assoc
  20004. * with a file or device and return it in 'info'.
  20005. * Returns 0 if successful, !0 if not. Error code
  20006. * contained in global '_doserrno'.
  20007. */
  20008.  
  20009. regs.x.ax = 0x4400; //DOS IOCTL get device info
  20010. regs.x.bx = handle;
  20011. intdos(®s, ®s );
  20012.  
  20013. *info = regs.x.dx; //assign device info word
  20014.  
  20015. return regs.x.cflag; //CY signals error
  20016. }
  20017.  
  20018. int_dos ioctl_read( int handle,
  20019. void *iobuf,
  20020. size_t nbytes)
  20021. {
  20022. /* Read ioctl data from a character device driver
  20023. * into 'iobuf'. Returns 0 if successful, !0 if not.
  20024. * Error code contained in global '_doserrno'. This
  20025. * call ends up as the device driver's cmd code 3,
  20026. * ioctl_read.
  20027. */
  20028.  
  20029. regs.x.ax = 0x4402; //DOS read ioctl data
  20030. regs.x.bx - handle;
  20031. regs.x.cx = nbytes;
  20032. sregs.ds = FP_SEG( iobuf );
  20033. regs.x.dx = FP_OFF( iobuf );
  20034. intdosx( ®s, ®s, &sregs );
  20035.  
  20036. return regs.x.cflag; //CY signals error
  20037. }
  20038.  
  20039. int _dos_ioctl write( int handle,
  20040. void *iobuf,
  20041. size_t nbytes )
  20042. {
  20043. /* Write ioctl data to a character device driver
  20044. * from 'iobuf'. Returns 0 if successful, !0 if not.
  20045. * Error code contained in global '_doserrno'. This
  20046. * call ends up as the device driver's cmd code 12,
  20047. * ioctl_write.
  20048. */
  20049.  
  20050. regs.x.ax = 0x4403; //DOS write ioctl data
  20051. regs.x.bx = handle;
  20052. regs.x.cx = nbytes;
  20053. sregs.ds - FP_SEG( iobuf );
  20054. regs.x.dx = FP_OFF( iobuf );
  20055. intdosx( ®s, ®s, &sregs );
  20056.  
  20057. return regs.x.cflag; //CY signals error
  20058. }
  20059.  
  20060. void print_menu(void )
  20061. {
  20062. char **p;
  20063.  
  20064. for( p = Menu; *p; p++ )
  20065. printf( "%s\n", *p );
  20066. }
  20067.  
  20068. main(int argc, char **argv )
  20069. {
  20070. int cmd;
  20071. size_t d_info;
  20072.  
  20073.  
  20074. ioHandle = open(Dname, O_RDWR);
  20075.  
  20076. if(ioHandle == -1) {
  20077. printf("Unable to open TAPE device\n");
  20078. return 1;
  20079. }
  20080.  
  20081. //Verify that TAPE is a device and that
  20082. //it supports ioctl calls.....
  20083.  
  20084. _dos_dev_info(ioHandle, &d_info);
  20085.  
  20086. if((d_info & IM_A_DEVICE) == 0) {
  20087. printf("Sorry,'%s' is a file\n", Dname);
  20088. return 1;
  20089. }
  20090.  
  20091. if( (d_info & IOCTL_OK) == 0) {
  20092. printf("Sorry, TAPE doesn't support IOCTL\n");
  20093. return 1;
  20094. }
  20095.  
  20096. //Initialize the device at 'boot-up' ....
  20097. ioArg.c_state = OFF;
  20098. ioArg.cmd = CMD_POWER_ON;
  20099. _dos_ioctl_write( ioHandle, (void*) &ioArg,
  20100. IO_BUF_SIZE);
  20101.  
  20102. //Execute user control loop .....
  20103. while( 1 ) {
  20104. print_menu();
  20105. printf( "Enter selection: ");
  20106. cmd = bioskey(0);
  20107. cmd &= 0x00ff; //zap scan code
  20108.  
  20109. if( cmd == 27 )
  20110. break;
  20111.  
  20112. printf("%c\n\n",cmd); //echo cmd
  20113. ioArg.cmd = cmd - '0'; //to integer
  20114. _dos_ioctl_read( ioHandle,
  20115. (void *) &ioArg,
  20116. IO_BUF_SIZE);
  20117.  
  20118. }
  20119.  
  20120. return 0;
  20121. }
  20122.  
  20123. /* ----- End of File -------------------------- */
  20124.  
  20125. Listing 7 Makefile for TAPE.SYS Demo Device Driver
  20126. MODEL = s
  20127. LIBS = \lib\c$(MODEL)
  20128. LINK = /m /s
  20129. COMPILE = -w -c
  20130. ASSEMBLE = /w+ /mx
  20131.  
  20132.  
  20133. .c.obj:
  20134. tcc $(COMPILE) -m$(MODEL) $*
  20135.  
  20136. .asm.obj:
  20137. tasm $(ASSEMBLE) $*
  20138.  
  20139. all: tape.sys
  20140.  
  20141. start.obj: start.asm
  20142.  
  20143. exec.obj: exec.c driver.h
  20144.  
  20145. tape.obj: tape.c driver.h tape.h
  20146.  
  20147. tape.exe: start.obj exec.obj tape.obj
  20148. tlink $(LINK) start exec tape,tape.exe,,$(LIBS)
  20149.  
  20150. tape.sys: tape.exe
  20151. exe2bin tape.exe tape.sys
  20152. del tape.exe
  20153.  
  20154.  
  20155.  
  20156.  
  20157.  
  20158.  
  20159.  
  20160.  
  20161.  
  20162.  
  20163.  
  20164.  
  20165.  
  20166.  
  20167.  
  20168.  
  20169.  
  20170.  
  20171.  
  20172.  
  20173.  
  20174.  
  20175.  
  20176.  
  20177.  
  20178.  
  20179.  
  20180.  
  20181.  
  20182.  
  20183.  
  20184.  
  20185.  
  20186.  
  20187.  
  20188.  
  20189.  
  20190.  
  20191.  
  20192.  
  20193.  
  20194.  
  20195.  
  20196. Standard C
  20197.  
  20198.  
  20199. The Header <signal.h>
  20200.  
  20201.  
  20202.  
  20203.  
  20204. P.J. Plauger
  20205.  
  20206.  
  20207. P.J. Plauger is senior editor of The C Users Journal. He is secretary of the
  20208. ANSI C standards committee, X3J11, and convenor of the ISO C standards
  20209. committee, WG14. His latest book is The Standard C Library, published by
  20210. Prentice-Hall. You can reach him care of The C Users Journal or via Internet
  20211. at pjp@plauger.uunet.uu.net.
  20212.  
  20213.  
  20214.  
  20215.  
  20216. Introduction
  20217.  
  20218.  
  20219. A signal is an extraordinary event that occurs during the execution of a
  20220. program. Synchronous signals occur because of actions that your program takes.
  20221. Division by zero is one example. Accessing storage improperly is another.
  20222. Asynchronous signals occur because of actions outside your program. Someone
  20223. striking an attention key is one example. A separate program (executing
  20224. asynchronously) signaling yours is another.
  20225. A signal that is not ignored by your program demands immediate handling. If
  20226. you do not specify handling for a signal that occurs, it is treated as a fatal
  20227. error. Your program terminates execution with unsuccessful status. In some
  20228. implementations, the status indicates which signal occurred. In others, the
  20229. Standard C library writes an error message to the standard error stream before
  20230. it terminates execution.
  20231. The header <signal.h> defines the code values for an open-ended set of
  20232. signals. It also declares two functions:
  20233. raise, which reports a synchronous signal
  20234. signal, which lets you specify the handling of a signal
  20235. You can handle a signal one of three ways:
  20236. default handling is to terminate execution, as described above
  20237. ignoring the signal effectively discards it
  20238. handling the signal causes control to pass to a function that you designate
  20239. In the last case, the function that you designate is called a signal handler.
  20240. The Standard C library calls a signal handler when its corresponding signal is
  20241. reported. Normal execution of the program is suspended. If the signal handler
  20242. returns to its caller, execution of the program resumes at the point where it
  20243. was suspended. Aside from the delay, and any changes made by the signal
  20244. handler, the behavior of the program is unaffected.
  20245.  
  20246.  
  20247. Synchronization
  20248.  
  20249.  
  20250. This sounds like elegant machinery, but it is not. The occurrence of a signal
  20251. introduces a second thread of control within a program. That raises all sorts
  20252. of issues about synchronization and reliable operation. The C Standard
  20253. promises little in either regard. C programs have been handling signals since
  20254. the earliest days of the language. Nevertheless, a portable program can safely
  20255. take very few actions within a signal handler.
  20256. One problem is the Standard C library itself. If called with valid arguments,
  20257. no library function should ever generate a synchronous signal. But an
  20258. asynchronous signal can occur while the library is executing. The signal may
  20259. suspend program execution part way through a print operation, for example.
  20260. Should the signal handler print a message, an output stream can end up in a
  20261. confused state. There is no way to determine from within a signal handler
  20262. whether a library function is in an unsafe state.
  20263. Another problem concerns data objects that you declare to have volatile types.
  20264. That warns the translator that surprising agents can access the data object,
  20265. so it is careful how it generates accesses to such a data object. In
  20266. particular, it knows not to perform optimizations that move the accesses to
  20267. volatile data objects beyond certain sequence points. A signal handler is, of
  20268. course, a surprising agent. Thus, you should declare any data object you
  20269. access within a signal handler to have a volatile type. That helps, provided
  20270. the signal is synchronous and occurs between two sequence points where the
  20271. data object is not accessed. For an asynchronous signal however, no amount of
  20272. protection suffices. Signals are not confined to suspending program execution
  20273. only at sequence points.
  20274. The C Standard offers a partial solution to the problem of writing reliable
  20275. signal handlers. The header <signal.h> defines the type sig_atomic_t. It is an
  20276. integer type that the program accesses atomically. A signal should never
  20277. suspend program execution part way through the access of a data object
  20278. declared with this type. A signal handler can share with the rest of the
  20279. program only data objects declared to have type volatile sig_atomic_t.
  20280.  
  20281.  
  20282. Shortcomings Of Signals
  20283.  
  20284.  
  20285. As a means of communicating information, signals leave much to be desired. The
  20286. semantics spelled out for signals in the C Standard is based heavily on their
  20287. behavior under the early UNIX operating system. That system had serious lapses
  20288. in the way it managed signals:
  20289. Multiple signals could get lost. The system did not queue signals, but
  20290. remembered only the last one reported. If a second signal occurred before a
  20291. handler processed the first, a signal could go unnoticed.
  20292. A program could terminate even when it endeavors to process all signals. When
  20293. control first passes to a signal handler, handling for that signal reverts to
  20294. default behavior. The signal handler must call signal to reestablish itself as
  20295. the handler for the signal. Should that signal occur between entry to the
  20296. handler and the call to signal, the default handler gets control and
  20297. terminates the program.
  20298. No mechanism exists for specifically terminating the handling of a signal. In
  20299. other operating systems, the program enters a special state. Processing of
  20300. subsequent signals blocks until the signal handler reports completion. On such
  20301. systems, other functions may have to assist in processing signals properly.
  20302. These can include abort and exit, declared in <stdlib.h>, and longjmp,
  20303. declared in <setjmp.h>.
  20304. Moreover, signals arise from an odd assortment of causes on any computer. The
  20305. ones named in the C Standard are a subset of those supported by UNIX. These in
  20306. turn derive from the interrupts and traps defined for the PDP-11. Mapping the
  20307. sources of signals for a given computer onto those defined for C is often
  20308. arbitrary. Mapping the semantics of signal handling for a given operating
  20309. systems can be even more creative.
  20310. The C Standard had to weaken the already weak semantics of UNIX signals to
  20311. accommodate an assortment of operating systems:
  20312. A given signal may never occur unless you report it with raise.
  20313. A given signal may be ignored unless you call signal to turn it on.
  20314. There's not much left.
  20315. Thus, no portable use for the functions declared in <signal.h> can be defined
  20316. with complete safety. You could, in principle, specify a handler for a signal
  20317. that only raise reports. It's hard to imagine a situation where that works
  20318. better than instead using setjmp and longjmp, declared in <setjmp.h>. Besides,
  20319. you cannot ensure that a given signal is never reported on an arbitrary
  20320. implementation of C. Any time your program handles signals, accept the fact
  20321. that you limit its portability.
  20322.  
  20323.  
  20324. What The C Standard Says
  20325.  
  20326.  
  20327.  
  20328.  
  20329.  
  20330. 7.7 Signal handling <signal.h>
  20331.  
  20332.  
  20333. The header <signal.h> declares a type and two functions and defines several
  20334. macros, for handling various signals (conditions that may be reported during
  20335. program execution).
  20336. The type defined is
  20337. sig_atomic_t
  20338. which is the integral type of an object that can be accessed as an atomic
  20339. entity, even in the presence of asynchronous interrupts.
  20340. The macros defined are
  20341. SIG_DFL
  20342. SIG_ERR
  20343. SIG_IGN
  20344. which expand to constant expressions with distinct values that have type
  20345. compatible with the second argument to and the return value of the signal
  20346. function, and whose value compares unequal to the address of any declarable
  20347. function; and the following, each of which expands to a positive integral
  20348. constant expression that is the signal number corresponding to the specified
  20349. condition:
  20350. SIGABRT -- abnormal termination, such as is initiated by the abort function
  20351. SIGFPE -- an erroneous arithmetic operation, such as zero divide or an
  20352. operation resulting in overflow
  20353. SIGILL -- detection of an invalid function image, such as an illegal
  20354. instruction
  20355. SIGINT -- receipt of an interactive attention signal
  20356. SIGSEGV -- an invalid access to storage
  20357. SIGTERM -- a termination request sent to the program
  20358. An implementation need not generate any of these signals, except as a result
  20359. of explicit calls to the raise function. Additional signals and pointers to
  20360. undeclarable functions, with macro definitions beginning, respectively, with
  20361. the letters SIG and an uppercase letter or with SIG_ and an uppercase
  20362. letter,108 may also be specified by the implementation. The complete set of
  20363. signals, their semantics, and their default handling is
  20364. implementation-defined; all signal numbers shall be positive.
  20365.  
  20366.  
  20367. 7.7.1 Specify signal handling
  20368.  
  20369.  
  20370.  
  20371.  
  20372. 7.7.1.1 The signal function
  20373.  
  20374.  
  20375.  
  20376.  
  20377. Synopsis
  20378.  
  20379.  
  20380. #include <signal.h>
  20381. void (*signal(int sig, void
  20382. (*func) (int))) (int);
  20383.  
  20384.  
  20385. Description
  20386.  
  20387.  
  20388. The signal function chooses one of three ways in which receipt of the signal
  20389. number sig is to be subsequently handled. If the value of func is SIG_DFL,
  20390. default handling for that signal will occur. If the value of func is SIG_IGN,
  20391. the signal will be ignored. Otherwise, func shall point to a function to be
  20392. called when that signal occurs. Such a function is called a signal handler.
  20393. When a signal occurs, if func points to a function, first the equivalent of
  20394. signal(sig, SIG_DFL); is executed or an implementation-defined blocking of the
  20395. signal is performed. (If the value of sig is SIGILL, whether the reset to
  20396. SIG_DFL occurs is implementation-defined.) Next the equivalent of (*func)
  20397. (sig); is executed. The function func may terminate by executing a return
  20398. statement or by calling the abort, exit, or longjmp function. If func executes
  20399. a return statement and the value of sig was SIGFPE or any other
  20400. implementation-defined value corresponding to a computational exception, the
  20401. behavior is undefined. Otherwise, the program will resume execution at the
  20402. point it was interrupted.
  20403. If the signal occurs other than as the result of calling the abort or raise
  20404. function, the behavior is undefined if the signal handler calls any function
  20405. in the standard library other than the signal function itself (with a first
  20406. argument of the signal number corresponding to the signal that caused the
  20407. invocation of the handler) or refers to any object with static storage
  20408. duration other than by assigning a value to a static storage duration variable
  20409. of type volatile sig_atomic_t. Furthermore, if such a call to the signal
  20410. function results in a SIG_ERR return, the value of errno is indeterminate.109
  20411. At program startup, the equivalent of
  20412. signal (sig, SIG_IGN);
  20413. may be executed for some signals selected in an implementation-defined manner;
  20414. the equivalent of
  20415. signal (sig, SIG_DFL);
  20416. is executed for all other signals defined by the implementation.
  20417. The implementation shall behave as if no library function calls the signal
  20418. function.
  20419.  
  20420.  
  20421. Returns
  20422.  
  20423.  
  20424.  
  20425. If the request can be honored, the signal function returns the value of func
  20426. for the most recent call to signal for the specified signal sig. Otherwise, a
  20427. value of SIG_ERR is returned and a positive value is stored in errno.
  20428. Forward references: the abort function (7.10.4.1), the exit function
  20429. (7.10.4.3).
  20430.  
  20431.  
  20432. 7.7.2 Send signal
  20433.  
  20434.  
  20435.  
  20436.  
  20437. 7.7.2.1 The raise function
  20438.  
  20439.  
  20440.  
  20441.  
  20442. Synopsis
  20443.  
  20444.  
  20445. #include <signal .h>
  20446. int raise(int sig);
  20447.  
  20448.  
  20449. Description
  20450.  
  20451.  
  20452. The raise function sends the signal sig to the executing program.
  20453.  
  20454.  
  20455. Returns
  20456.  
  20457.  
  20458. The raise function returns zero if successful, nonzero if unsuccessful.
  20459. Footnotes:
  20460. 108. See "future library directions" (7.13.5). The names of the signal numbers
  20461. reflect the following terms (respectively): abort, floating-point exception,
  20462. illegal instruction, interrupt, segmentation violation, and termination.
  20463. 109. If any signal is generated by an asynchronous signal handler, the
  20464. behavior is undefined.
  20465.  
  20466.  
  20467. Using <signal.h>
  20468.  
  20469.  
  20470. Signal handling is essentially nonportable. Use the functions declared in
  20471. <signal.h> only when you must specify the handling of signals for a known set
  20472. of operating systems. Don't try too hard to generalize the code.
  20473. If default handling for a signal is acceptable, then by all means choose that
  20474. option. Adding your own signal handler decreases portability and raises the
  20475. odds that the program will mishandle the signal. If you must provide a handler
  20476. for a signal, categorize it as follows:
  20477. a handler for a signal that must not return, such as SIGFPE reporting an
  20478. arithmetic exception or SIGABRT reporting a fatal error
  20479. a handler for a signal that must return, such as SIGINT reporting an attention
  20480. interrupt that may have interrupted a library operation
  20481. As a rule, the second category contains asynchronous signals not intended to
  20482. cause immediate program termination. Rarely will you find a signal that does
  20483. not fit clearly in one of these categories.
  20484. A signal handler that must not return ends in a call to abort, exit, or
  20485. longjmp. Do not, of course, end a handler for SIGABRT with a call to abort.
  20486. The handler should not reestablish itself by calling signal. Leave that to
  20487. some other agency, if the program does not terminate. If the signal is
  20488. asynchronous, be wary of performing any input or output. You may have
  20489. interrupted the library part way through such an operation.
  20490. A signal handler that must return ends in a return statement. If it is to
  20491. reestablish itself, it should do so immediately on entry. If the signal is
  20492. asynchronous, store a nonzero value in a volatile data object of type
  20493. sig_atomic_t. Do nothing else that has side effects visible to the executing
  20494. program, such as input or output and accessing other data objects.
  20495. A sample asynchronous signal handler might look like:
  20496. #include <signal.h>
  20497.  
  20498. static sig_atomic_t intflag = 0;
  20499.  
  20500. static void field_int(int sig)
  20501. { /* handle SIGINT */
  20502. signal (SIGINT, &field_int);
  20503. intflag = 1;
  20504. return;
  20505. }
  20506. The program calls signal(SIGINT, &field_int) to establish the handler. From
  20507. time to time, it can then check for the occurrence of asynchronous interactive
  20508. attention interrupts by executing code such as:
  20509. if (intflag)
  20510.  
  20511. { /* act on interrupt */
  20512. intflag = 0;
  20513. .....
  20514. }
  20515. Note that two small windows exist where these signals can go astray:
  20516. Within field_int before the call to signal, an occurrence of SIGINT can
  20517. terminate the program.
  20518. Between the testing and clearing of intflag, an occurrence of SIGINT can be
  20519. lost.
  20520. Those are inherent limitations of signals.
  20521.  
  20522.  
  20523. Standard Signals
  20524.  
  20525.  
  20526. Here is a brief characterization of the signals defined for all
  20527. implementations of Standard C. Note that a given implementation may well
  20528. define more. Display the contents of <signal.h> for other defined macro names
  20529. that begin with SIG. These should expand to (small) positive integers that
  20530. represent additional signals.
  20531. SIGABRT -- This signal occurs when the program is terminating unsuccessfully,
  20532. as by an explicit call to abort, declared in <stdlib.h>. Do not ignore this
  20533. signal. If you provide a handler, do as little as possible. End the handler
  20534. with a return statement or a call to exit, declared in <stdlib.h>.
  20535. SIGFPE -- The name originally meant "floating-point exception." The C Standard
  20536. generalizes this signal to cover any arithmetic exception such as overflow,
  20537. underflow, or zero divide. Implementations vary considerably on what
  20538. exceptions they report, if any. Rarely does an implementation report integer
  20539. overflow. Ignoring this signal may be rash. A handler must not return.
  20540. SIGINT -- This is the conventional way of reporting an asynchronous
  20541. interactive attention signal. Most systems provide some keystroke combination
  20542. that you can type to generate such a signal. Examples are ctl-C, DEL, and
  20543. ATTN. It offers a convenient way to terminate a tiresome loop early. But be
  20544. aware that an asynchronous signal can catch the program part way through an
  20545. operation that should be atomic. If the handler does not return control, the
  20546. program may subsequently misbehave. You can safely ignore this signal.
  20547. SIGSEGV -- The name originally meant "segmentation violation," because the
  20548. PDP-11 managed memory as a set of segments. The C Standard generalizes this
  20549. signal to cover any exception raised by an invalid storage access. The program
  20550. has attempted to access storage outside any of the functions or data objects
  20551. defined by C, as with an ill-formed function designator or lvalue. Or the
  20552. program has attempted to store a value in a data object with a const type. In
  20553. any event, the program cannot safely continue execution. Do not ignore this
  20554. signal or return from its handler.
  20555. SIGTERM -- This signal is traditionally sent from the operating system or from
  20556. another program executing asynchronously with yours. Treat it as a polite but
  20557. firm request to terminate execution. It is an asynchronous signal, so it may
  20558. occur at an inopportune point in your program. You may want to defer it, using
  20559. the techniques described above. You can ignore this signal safely, although it
  20560. may be bad manners to do so.
  20561.  
  20562.  
  20563. Implementing <signal.h>
  20564.  
  20565.  
  20566. Listing 1 shows the file signal.h. The header <signal.h> I present here is
  20567. minimal. A UNIX system, for example, defines dozens of signals. Many systems
  20568. endeavor to look as much as possible like UNIX in this regard. They too define
  20569. all these signals even if they do not generate many of them. Notwithstanding
  20570. this concerted group behavior, the choice of signals and their codes both vary
  20571. considerably. I have endeavored here to choose codes that are most widely
  20572. used.
  20573. As usual, I make use of the internal header <yvals.h> to provide parameters
  20574. that can vary among systems. The code for SIGABRT is one. The highest valid
  20575. signal code is another. Some functions in this implementation use the macro
  20576. _NSIG to determine the lowest positive number that is not a valid signal code.
  20577. Thus, the header <yvals.h> defines two macros of interest here. For a typical
  20578. UNIX system, the definitions are:
  20579. #define _SIGABRT 6
  20580. #define _SIGMAX 32
  20581. The header <signal.h> makes an additional concession to widespread UNIX
  20582. practice. It defines the macros SIG_ERR and SIG_IGN in a moderately ugly way.
  20583. The values -1 and 1 could conceivably be valid function addresses in some
  20584. implementation. Admittedly, that is only rarely possible. Where it is
  20585. possible, the linker can be jiggered to avoid the possibility. Still, other
  20586. values would be more gracious. (The addresses of signal and raise, for
  20587. example, are not likely to specify useful signal handlers.) But the values
  20588. chosen here are the ones used widely in UNIX implementations. They are also
  20589. widely imitated under other operating systems. I chose these for compatibility
  20590. with existing machinery.
  20591. That compatibility is often necessary. Almost invariably, the functions signal
  20592. and raise must be tailored for each operating system. UNIX is the extreme
  20593. case. In that environment, the system service signal does the whole job. If
  20594. you have access to a C-callable function of that name, just use it. Let other
  20595. functions call it directly. If the system service has a private name, such as
  20596. _Signal, you can write signal as:
  20597. /* signal function - UNIX version */
  20598. #include <signal.h>
  20599.  
  20600. _Sigfun *_Signal(int, _Sigfun *)
  20601.  
  20602. _Sigfun *(signal)(int sig,
  20603. _Sigfun *fun)
  20604. { /* call the system service */
  20605. return (_Signal(sig, fun));
  20606. }
  20607. This is an obvious candidate for a masking macro in <signal.h>.
  20608. The function raise is only slightly more difficult. It uses the system service
  20609. kill to send a signal to itself. ("Kill" is a misnomer stemming from its
  20610. earliest use for sending only the signal SIGKILL.) To identify itself, raise
  20611. also needs the system service getpid. Assuming suitable secret names for these
  20612. two system services, such as _Kill and _Getpid, you can write raise as:
  20613. /* raise function - UNIX version */
  20614. #include <signal.h>
  20615.  
  20616. int _Getpid (void);
  20617. int _Raise(int, int);
  20618.  
  20619. int (raise)(int sig}
  20620. { /* raise a signal */
  20621. return (_Kill(_Getpid(), sig));
  20622. }
  20623. Here is another obvious candidate for a masking macro.
  20624. I have also written more generic versions of signal and raise, but I lack the
  20625. space to present them here. They manage a table of pointers to signal handlers
  20626. for an environment that does less of this work for you. Of course, you still
  20627. have to add system-specific code to handle any hardware interrupts. As I
  20628. pointed out earlier, there's no such thing as portable signals.
  20629. This article is excerpted from P.J. Plauger, The Standard C Library,
  20630. (Englewood Cliffs, N.J.: Prentice-Hall, 1992).
  20631.  
  20632. Listing 1
  20633. /* signal.h standard header */
  20634. #ifndef _SIGNAL
  20635.  
  20636. #define _SIGNAL
  20637. #ifndef _YVALS
  20638. #include<yvals.h>
  20639. #endif
  20640. /* type definitions */
  20641. typedef int sig_atomic_t;
  20642. typedef void _Sigfun(int);
  20643. /* signal codes */
  20644. #define SIGABRT _SIGABRT
  20645. #define SIGINT 2
  20646. #define SIGILL 4
  20647. #define SIGFPE 8
  20648. #define SIGSEGV 11
  20649. #define SIGTERM 15
  20650. #define _NSIG _SIGMAX /* one more than last code */
  20651. /* signal return values */
  20652. #define SIG_DFL (_Sigfun *)0
  20653. #define SIG_ERR (_Sigfun *)-1
  20654. #define SIG_IGN (_Sigfun *)1
  20655. /* declarations */
  20656. int raise(int);
  20657. _Sigfun *signal(int, _Sigfun *);
  20658. #endif
  20659.  
  20660. /* End of File */
  20661.  
  20662.  
  20663.  
  20664.  
  20665.  
  20666.  
  20667.  
  20668.  
  20669.  
  20670.  
  20671.  
  20672.  
  20673.  
  20674.  
  20675.  
  20676.  
  20677.  
  20678.  
  20679.  
  20680.  
  20681.  
  20682.  
  20683.  
  20684.  
  20685.  
  20686.  
  20687.  
  20688.  
  20689.  
  20690.  
  20691.  
  20692.  
  20693.  
  20694.  
  20695.  
  20696.  
  20697.  
  20698.  
  20699. Doctor C's Pointers(R)
  20700.  
  20701.  
  20702. Data Structures, Part 10
  20703.  
  20704.  
  20705.  
  20706.  
  20707. Rex Jaechke
  20708.  
  20709.  
  20710. Rex Jaeschke is an independent computer consultant, author, and seminar
  20711. leader. He participates in both ANSI and ISO C Standards meeting and is the
  20712. editor of The Journal of C Translation, a quarterly publication aimed at
  20713. implementors of C language translation tools. Readers are encouraged to submit
  20714. column topics and suggestions to Rex at 2051 Swans Neck Way, Reston, VA 22091
  20715. or via UUCP at rex@aussie.com.
  20716.  
  20717.  
  20718.  
  20719.  
  20720. Stacks, Continued
  20721.  
  20722.  
  20723. Last month I talked about evaluating expressions. While a stack can be useful
  20724. for this task, it does require that the expression be rearranged. For example,
  20725. in programming languages we write arithmetic expressions using infix notation.
  20726. That is, binary operators go between their operands as in (a + (b * c)).
  20727. It would be very advantageous to be able to rewrite this expression in the
  20728. order in which the operations are to be evaluated. You can do this by taking
  20729. the precedence into account. This method, called postfix notation, completely
  20730. removes the need for grouping parentheses. The previous expression written in
  20731. postfix notation becomes a b c *+.
  20732. Now the multiplication applies to the two operands preceding it and the
  20733. addition applies to that result and the operand preceding that, namely a.
  20734. Expressions written in postfix notation lend themselves directly to being
  20735. evaluated using a stack. For example, an H-P calculator requires you to enter
  20736. expressions in postfix notation. This is often referred also as Reverse Polish
  20737. Notation (RPN), since it is opposite to Polish (or prefix) notation. The trick
  20738. then is to convert an infix expression to a postfix expression. Listing 1,
  20739. Listing 2, and Listing 3 show much of the solution.
  20740. The main program prompts for and reads in an expression in infix notation,
  20741. converts to the corresponding postfix notation, and prints the resulting
  20742. string. It ignores white space in the input string. Figure 1 shows inputs and
  20743. their results.
  20744. The real work is done by the function intopost shown in Listing 2. The stack
  20745. manipulation functions are shown in Listing 3.
  20746. The conditional compilation code using TRACE is useful both in debugging and
  20747. in demonstrating what is actually being pushed and popped along the way.
  20748. Figure 2 shows an example.
  20749. This program goes a long way toward handling infix expressions but it clearly
  20750. has some limitations. First, it handles only variable names, and then only
  20751. those one character long. In reality it would need to handle constants as well
  20752. and longer variable names. It would also have to maintain some kind of symbol
  20753. table for each name encountered, along with type information since it can have
  20754. mixed-mode arithmetic involving any arithmetic types of variables and
  20755. constants. Also, the conversion function can only handle addition,
  20756. subtraction, multiplication, and division. It is easy to add other operators
  20757. but you would need to handle the precedence issue in a more general way. In
  20758. intopost, precedence is hard-wired whereas some kind of table lookup would be
  20759. more efficient as well as elegant.
  20760. Next month we'll look at stacks that handle different object types and stacks
  20761. that share storage.
  20762. Figure 1
  20763. Enter infix: a + b
  20764.  postfix: ab+
  20765. Enter infix: (((((a + b)))))
  20766.  postfix: ab+
  20767. Enter infix: c - d * e + f
  20768.  postfix: cde*-f+
  20769. Enter infix: c - d * (e + f)
  20770.  postfix: cdef+*-
  20771. Enter infix: (c - d * e) + f
  20772.  postfix: cde*-f+
  20773. Enter infix: c - (d * (e + f))
  20774.  postfix: cdef+*-
  20775. Enter infix: (c - d) * e + f
  20776.  postfix: cd-e*f+
  20777. Enter infix: (c - d) * (e + f)
  20778.  postfix: cd-ef+*
  20779. Enter infix: (((a - b) / (c + d)) * e)
  20780.  postfix: ab-cd+/e*
  20781.  
  20782. Figure 2
  20783. Enter infix: c - d * (e + f)
  20784. pushing: (
  20785. *infix: c
  20786. *infix:
  20787. *infix: -
  20788. popping: (
  20789. pushing: (
  20790. pushing: -
  20791. *infix:
  20792.  
  20793. *infix: d
  20794. *infix:
  20795. *infix: *
  20796. popping: -
  20797. pushing: -
  20798. pushing: *
  20799. *infix:
  20800. *infix: (
  20801. pushing: (
  20802. *infix: e
  20803. *infix:
  20804. *infix: +
  20805. popping: (
  20806. pushing: (
  20807. pushing: +
  20808. *infix:
  20809. *infix: f
  20810. *infix: )
  20811. popping: +
  20812. popping: (
  20813. popping: *
  20814. popping: -
  20815. popping: (
  20816.  postfix: cdef+*-
  20817.  
  20818. Listing 1
  20819. /* convert infix notation to postfix notation */
  20820.  
  20821. #include <stdio.h>
  20822.  
  20823. main()
  20824. {
  20825. char infix_str[200];
  20826. char postfix_str[200];
  20827. void intopost(const char *infix, char *postfix);
  20828.  
  20829. while (1) {
  20830. printf("Enter infix: ");
  20831. if (gets(infix_str) == NULL)
  20832. break;
  20833.  
  20834. intopost(infix_str, postfix_str);
  20835. printf(" postfix: %s\n", postfix_str);
  20836. }
  20837.  
  20838. return 0;
  20839. }
  20840.  
  20841. /* End of File */
  20842.  
  20843.  
  20844. Listing 2
  20845. #include <ctype.h>
  20846.  
  20847. void push(int);
  20848. int pop(void);
  20849.  
  20850. /*------------------------------------------------------------------
  20851.  
  20852.  
  20853. FUNCTION: intopost - converts infix to postfix
  20854.  
  20855. A. Push a ( on the stack. This sentinel allows us to detect when we have
  20856. flushed out the stack on completion in step I.
  20857.  
  20858. --- Perform steps B through H for each character in the infix string ---
  20859.  
  20860. B. Ignore white space.
  20861.  
  20862. C. Pass alphabetic characters through to postfix list.
  20863.  
  20864. D. Push any ( on the stack. These sentinels allows us to detect when we
  20865. have flushed out the stack when handling ) and operators.
  20866.  
  20867. E. Have a ) so pop off the stack and put into postfix list until a ( is
  20868. popped. Discard that (.
  20869.  
  20870. F. Have a * or /. Pop off any operators of equal or higher precedence
  20871. and put them into postfix list. If a ( or lower precedence operator (such
  20872. as + or -) is popped, put it back and stop looking. Push new * or /.
  20873.  
  20874. G. Have a + or -. Pop off any operators of equal or higher precedence
  20875. (that includes all of them) and put them into postfix list. If a ( is
  20876. popped, put it back and stop looking. Push new + or -.
  20877.  
  20878. H. Report unknown character on input.
  20879.  
  20880. ------
  20881.  
  20882. I. Have processed all input characters. Now flush stack until we find
  20883. the matching ( put there in step A.
  20884.  
  20885. J. Terminate the postfix list.
  20886.  
  20887. --------------------------------------------------------------------*/
  20888.  
  20889. void intopost(const char *infix, char *postfix)
  20890. {
  20891. int st;
  20892.  
  20893. /*A*/ push (' (');
  20894. while (*infix != '\0') {
  20895.  
  20896. #ifdef TRACE
  20897. printf("*infix: %c\n", *infix);
  20898. #endif
  20899.  
  20900. /*B*/ if (isspace(*infix)) {
  20901. ;
  20902. }
  20903. /*C*/ else if (isalpha(*infix)) {
  20904. * postfix++ = *infix;
  20905. }
  20906. /*D*/ else if (*infix == '(') {
  20907. push('(');
  20908. }
  20909. /*E*/ else if (*infix == ')') {
  20910. while ((st = pop()) != '(')
  20911. * postfix++ = st;
  20912.  
  20913. }
  20914. /*F*/ else if (*infix == '*' *infix == '/') {
  20915. while (1) {
  20916. if ((st = pop()) == '(' st == '+'
  20917.  st == '-') {
  20918. push(st);
  20919. break;
  20920. }
  20921. *postfix++ = st;
  20922. }
  20923. push(*infix);
  20924. }
  20925. /*G*/ else if (*infix == '+' *infix == '-') {
  20926. while (1) {
  20927. if ((st = pop()) == '(') {
  20928. push(st);
  20929. break;
  20930. }
  20931. *postfix++ = st;
  20932. }
  20933. push(*infix);
  20934. }
  20935. /*H*/ else {
  20936. printf("Unknown input character %c\n", *infix);
  20937. }
  20938.  
  20939. ++infix;
  20940. continue;
  20941. }
  20942.  
  20943. /*I*/ while ((st = pop()) != '(')
  20944. *postfix++ = st;
  20945.  
  20946. /*J*/ *postfix = '\0';
  20947.  
  20948. /* End of File */
  20949.  
  20950.  
  20951. Listing 3
  20952. #include <stdio.h>
  20953.  
  20954. #define STACK_SIZE 30
  20955.  
  20956. static int stack[STACK_SIZE]
  20957.  
  20958. static size_t stack_ptr = 0;
  20959.  
  20960. void push(int value)
  20961. {
  20962.  
  20963. #ifdef TRACE
  20964. printf("pushing: %c\n", value);
  20965. #endif
  20966.  
  20967. if (stack_ptr == STACK_SIZE)
  20968. printf("Stack is full\n");
  20969. else
  20970. stack[stack_ptr++] = value;
  20971. }
  20972.  
  20973.  
  20974. int pop(void)
  20975. {
  20976. if (stack_ptr == 0) {
  20977. printf("Stack is empty\n");
  20978. return 0;
  20979. }
  20980.  
  20981. #ifdef TRACE
  20982. printf("popping: %c\n", stack[stack_ptr - 1]);
  20983. #endif
  20984.  
  20985. return stack[--stack_ptr];
  20986. }
  20987.  
  20988. /* End of File */
  20989.  
  20990.  
  20991.  
  20992.  
  20993.  
  20994.  
  20995.  
  20996.  
  20997.  
  20998.  
  20999.  
  21000.  
  21001.  
  21002.  
  21003.  
  21004.  
  21005.  
  21006.  
  21007.  
  21008.  
  21009.  
  21010.  
  21011.  
  21012.  
  21013.  
  21014.  
  21015.  
  21016.  
  21017.  
  21018.  
  21019.  
  21020.  
  21021.  
  21022.  
  21023.  
  21024.  
  21025.  
  21026.  
  21027.  
  21028.  
  21029.  
  21030.  
  21031.  
  21032.  
  21033.  
  21034.  
  21035.  
  21036. Questions & Answers
  21037.  
  21038.  
  21039. Using Code Generators For Creating C Code
  21040.  
  21041.  
  21042.  
  21043.  
  21044. Ken Pugh
  21045.  
  21046.  
  21047. Kenneth Pugh, a principal in Pugh-Killeen Associates, teaches C language
  21048. courses for corporations. He is the author of C Language for Programmers and
  21049. All On C, and was a member on the ANSI C committee. He also does custom C
  21050. programming for communications, graphics, image databases, and hypertext. His
  21051. address is 4201 University Dr., Suite 102, Durham, NC 27707. You may fax
  21052. questions for Ken to (919) 489-5239. Ken also receives email at kpugh@dukemvs.
  21053. ac.duke.edu (Internet).
  21054.  
  21055.  
  21056. Q
  21057. I've read many of your books and articles. They are very enlightening, thank
  21058. you. I'm posting this for a friend who is a business manager. He's been given
  21059. the task of writing C programs, but he doesn't know C that well. He's heard
  21060. that a code generator might help. What is it? Is it worth getting? Where can
  21061. he get one if it is worth while? Thanks for your time in reading this note, I
  21062. know you're a busy individual.
  21063. Steve Kohut
  21064. New York
  21065. A
  21066. Code generators ease the creation of screens and reports. They go a step
  21067. beyond demo programs, such as Dan Bricklin's, in not just demonstrating a user
  21068. interface, but also in turning it into C code. That may complete the input
  21069. portion of your program, but you still have to write the process portion. For
  21070. example, the placement and colors for name and address fields in a mailing
  21071. list program can be created by the generator. But any custom functions (such
  21072. as comparing the ZIP code to the state for agreement) and the disk operations
  21073. for storing and retrieving the names must be written by you.
  21074. There are several available from the companies that sell programming tools
  21075. (such as the ones advertising here in The C Users Journal). The only one I
  21076. have used for the C language are C-scape's Look and Feel from the Oakland
  21077. Group (now Liant). It was a version from 1988, so the product may have
  21078. additional features by now.
  21079. You should look at the model that the screen package uses -- the numbers and
  21080. types of functions. If you can understand the model, then modifying the source
  21081. that is created by the package will be simple. C-scape has a fairly simple
  21082. model. It is easy to determine from the code where a particular field is being
  21083. input.
  21084. On a similar note at the recent C-forum, I had the opportunity to see a demo
  21085. of Liana, by Base Technology (1543 Pine Street, Boulder, Colorado 80302; (303)
  21086. 440-4558). This is a C-like language interpreter with an object-oriented
  21087. approach to MS Windows. You can not only create windows, but perform many
  21088. standard operations (such as text editing, printing, DDE). It is not a code
  21089. generator in the standard sense of the term. But it is such an easy-to-use
  21090. interface to Windows that it yields the same benefits for the C programmer
  21091. trying to create a Window program.
  21092. By the way, there are other C code generators that are used for special
  21093. purposes. If you are doing any work with language processing, I heartily
  21094. recommend lex and yacc. If any reader knows of other generators for any
  21095. purpose, please let me know and I'll pass them along.
  21096. Q
  21097. I've been working in C just a short while and only now have had to use
  21098. pointers. The following mini program (Listing 1) is a slight adaptation of an
  21099. example program copied from C Primer Plus by Mitchell Waite et al. It yields
  21100. different results depending on the memory model that is used during the
  21101. compilation, as shown in Figure 1 and Figure 2. Perhaps it's only a printf bug
  21102. (or feature) but it's got me puzzled.
  21103. As I understand it, the three models that don't work use either far or huge
  21104. memory pointers, whereas the three that do work use near pointers. With that
  21105. in mind I attempted to specify the use of near pointers in either or both of
  21106. the int declaration lines (i.e. int near *ptr) while the compiler was
  21107. defaulting to one of the three larger models. The results were the same, that
  21108. is, it worked incorrectly.
  21109. Is this a documented problem or am I doing something stupid or am I misusing
  21110. the Turbo C compiler or what? Any help would be appreciated.
  21111. Tom Frank
  21112. Austin, Texas
  21113. A
  21114. With the %u format specifier, you are asking printf to print an unsigned
  21115. integer. The size of a large memory model pointer (or near pointer) is two
  21116. bytes, or the size of an integer. When you compiled it with the small model,
  21117. the size of the pointers being passed to printf agreed with that designated by
  21118. the format specifier.
  21119. The size of a large memory model pointer (or far pointer) is four bytes, or
  21120. the size of a long integer. When you used the large memory model, the size of
  21121. the pointers was twice that specified by the format. Each half of the pointer
  21122. values that were passed was treated as an integer by printf.
  21123. On a PC, the bytes of an integer or pointer are in reverse order. So the
  21124. values that were printed out represented the two least significant bytes
  21125. followed by the two most significant bytes. The value of ptr was really equal
  21126. to
  21127. 6207 * 65536 + 4056
  21128. You can see the same phenomena if you print longs on a PC using the integer
  21129. format, as shown in Listing 2.
  21130. You can avoid the memory model problem by using the new ANSI format specifier
  21131. %p. This prints out the value of the pointer using the size based on the
  21132. memory model specified at compile time.
  21133. Q
  21134. What books would you recommend as the best for intermediate to advanced
  21135. programming of 2-D and 3-D animation and saving and loading PCX and GIF
  21136. picture files in Microsoft C v6? If none of the books covers v6 is there
  21137. anything for QuickC v2?
  21138. Could you also recommend a book on BIOS, DOS, and hardware addresses for
  21139. programmers?
  21140. Would I be better off using C++ for graphics programming?
  21141. Bruce Balstad
  21142. Hoffman Estates, IL
  21143. A
  21144. I have to admit I have simplified my life when it comes to graphics. I wrote
  21145. my own PCX loader originally using the PCX file documentation from Z-Soft.
  21146. Later I bought Essential Graphics (from Essential Software, 76 So. Orange, So.
  21147. Orange NJ 07079 (201) 762-6965). It handles most all the options in PCX files.
  21148. To handle all other graphics, I simply convert them to PCX using Hijaak.
  21149. Essential Graphics has lots of drawing functions. I am not wild about the
  21150. names they have used, but they work. They are a little slow for animation.
  21151. In regards to DOS, pick up a copy of MS-DOS Programmer's Reference book. It's
  21152. complete (except for a few undocumented calls). For the BIOS, the IBM
  21153. Technical Reference manual is available (at a bit of a price). The earlier
  21154. versions included the assembly source code listings. I haven't bought one
  21155. recently.
  21156. Perhaps our reader's have some suggestions for graphic books.
  21157. Q
  21158. I am currently working on a business application that involves a large amount
  21159. of reading and writing to inventory style data files but due to the fact that
  21160. most of my knowledge comes from books and not experience, I am running into a
  21161. problem that is setting me behind schedule. The problem is that I just can't
  21162. seem to find the correct codes that will allow the user to delete an item from
  21163. the inventory file. I have tried writing the hex value of the delete key to
  21164. the selected bytes of the file, but instead of deleting the chosen
  21165. character(s) the program displays the ASCII representative of the delete key
  21166. to the selected bytes.
  21167. Is there any simple solution to this problem that will allow me to accomplish
  21168. this task or do you know of any books that would help me to find out what I am
  21169. doing wrong?
  21170. Your help would be greatly appreciated. I hope to hear from you soon.
  21171. Michael Messuri
  21172. Moline, IL
  21173. A
  21174. This is a tough one, since you didn't specify the database that you are using.
  21175. On many database systems, a record is marked as deleted if the first byte is a
  21176. special character (e.g. OXFF or OX7F). When the data file is read sequentially
  21177. (without an index), any records with this byte are usually ignored by the
  21178. database. If the data file is read using an index, then when the record is
  21179. accessed, the database may return the record as a valid one. It would be up to
  21180. your code to check for deleted records.
  21181. If the data file is re-indexed after a deletion is made, then the deleted
  21182. record will not be indexed. If the data file is compacted, then the record
  21183. will be physically deleted.
  21184.  
  21185. Q
  21186. I have a program problem that is driving me crazy. I figure the answer should
  21187. be quite easy for a C programming veteran like you. In a BASIC program (please
  21188. don't give up on me yet) I use the following routines (Listing 3) to save or
  21189. load the Hercules graphics screen to/from disk.
  21190. I would like to do the same thing in C. My routine (Listing 4) does save a
  21191. 32767 byte file however, it is not the Hercules screen. What am I doing wrong?
  21192. I feel very stupid asking you about this. This one has me bewildered. Your
  21193. help is appreciated. Thanks.
  21194. James A. Gant
  21195. Oklahoma City, OK
  21196. A
  21197. Don't worry about feeling stupid. That happens to me sometimes when I stare at
  21198. a block of code for a few hours and realize that "for lack of a semicolon, a
  21199. program was lost."
  21200. Your code appears on the surface to be fine. You must compile it with the
  21201. large memory model. If you do not, then the library functions will use the
  21202. small memory model. And the header files that will be included are those for
  21203. the small model.
  21204. If the compiler you are using has prototypes, then the following will occur.
  21205. Inside <stdio.h> is the following statement. (To simplify, I've substituted
  21206. int for size_t).
  21207. int fwrite(void *address, int size,
  21208. int count, FILE *file_pointer);
  21209. Inside your program, as you have written it is the statement
  21210. fwrite(hga_buff, 1, 32767, fp);
  21211. Since a prototype is in scope, and the parameters are assignment compatible,
  21212. then the parameters will undergo a silent conversion. The only one that does
  21213. not match is the first one. So it will be as if you had written
  21214. fwrite ( (void near *) hga_buffer, 1, 32767, fp);
  21215. Note that the void * address of the prototype will be interpreted as a near
  21216. pointer, since the file is compiled in the small model. The data that is
  21217. written out would start at location 0 (the lower order two bytes of the
  21218. address).
  21219. Now you should get a warning message about "suspicious pointer conversion"
  21220. since you are converting a far pointer to a near pointer. But the warning is
  21221. not required by ANSI and if you have warnings turned off, you won't see it.
  21222. If the compiler does not have prototypes, the conversion will not take place,
  21223. but the writing to the file should have failed.
  21224.  
  21225.  
  21226. main () Placement
  21227.  
  21228.  
  21229. I'm really behind on my reading, so I'll assume your incomplete answer in the
  21230. July issue of The C Users Journal, pages 106 and 107, has already been pointed
  21231. out. But just in case... The question was, why are functions normally
  21232. main ()
  21233. {}
  21234. func1 ()
  21235. {}
  21236. func2 ()
  21237. {}
  21238. func3 ()
  21239. {}
  21240. but sometimes
  21241. func1 ()
  21242. {}
  21243. func2 ()
  21244. {}
  21245. func3 ()
  21246. {}
  21247. main ()
  21248. {}
  21249. In days of old, before prototyping in ANSI C made function declaration common,
  21250. there was a problem with pointers to functions. The compiler had to know of
  21251. the function before it would allow a pointer to it. This could be handled by
  21252. main()
  21253. {
  21254. int func1 (), func2();
  21255. float func3() ;
  21256. . . .
  21257. Code
  21258. . . .
  21259.  
  21260. }
  21261. But it is more often handled, such as in CU, by coding the functions to be
  21262. pointed to by function pointers before the code that will set pointers to said
  21263. functions.
  21264. There also was the quirky thing in many compilers that any variables listed
  21265. outside a function were considered global from that point on down. So one
  21266. could have functions that were not supposed to be able to access global
  21267. variables that other functions in this file were enabled to do so. (Other
  21268. files had access to all non-static globals in this file.)
  21269. I'm assuming that Lyle O. Haga was looking at old code. I haven't seen these
  21270. techniques in common use since C/UNIX stopped being mostly a PDP/11
  21271. phenomenon.
  21272. Doug Parsons
  21273. Boston, MA
  21274. Thanks for your comments. Actually that wasn't a quirky thing in the compiler.
  21275. In K&R, page 206, the rule for external identifiers was "from the definition
  21276. to the end of the source file in which they appear." That simplified the
  21277. compiler since any variable name that was found in a function had to have been
  21278. declared prior to its use.
  21279. The ANSI rules state that the external definition has file scope. This means
  21280. that it can be placed anywhere in the source file.
  21281.  
  21282. In reality, most people place all external definitions at the head of the
  21283. source file. Relying on the scoping rules of K&R as you described makes the
  21284. maintenance a bit of a nightmare. (KP)
  21285.  
  21286.  
  21287. DLLs for DOS
  21288.  
  21289.  
  21290. In the September 1991 issue of CUJ, David Qualls asks about support for
  21291. "dynamic linking" in DOS. Although it currently supports only UNIX, and not
  21292. DOS, he might want to take a look at dld by W. Wilson Ho. dld is (quoting its
  21293. documentation) "a library package of C functions that performs dynamic link
  21294. editing."
  21295. Programs that use dld can add compiled object code to or remove such code from
  21296. a process anytime during its execution. Loading modules, searching libraries,
  21297. resolving external references, and allocating storage for global and static
  21298. data structures are all performed at runtime. dld is now available for VAX,
  21299. Sun3, SPARCstation, Sequent Symmetry, and Atari ST. dld is distributed under
  21300. the terms of the GNU copyright agreement (and can be obtained from most GNU
  21301. archive sites), so source code is freely available.
  21302. It might be possible to implement the code under DOS, but doing so would
  21303. doubtless require a thorough understanding of the structure of DOS .OBJ, .EXE,
  21304. and .LIB files. Perhaps some keen programmer somewhere would like to
  21305. volunteer?
  21306. Eric Zurcher
  21307. Canberra Australia
  21308. Any volunteers? (KP)
  21309.  
  21310.  
  21311. Memory Resident Utilities
  21312.  
  21313.  
  21314. In response to Moses Mwarari Maina's question in the September, 1991 issue, I
  21315. know of two different sources for writing TSRs. The first is Turbo C
  21316. Memory-Resident Utilities, Screen I/O and Programming Techniques, by Al
  21317. Stevens from MIS Press. It has two chapters with about 80 pages on the
  21318. subject. The second is the TesSeRact TM Ram-Resident Development System, from
  21319. Innovative Data Concepts, Inc., 122 North York Road, Suite 5, Hatboro, PA
  21320. 19040. Their order phone number is 1-800-926-4551. I have only played with
  21321. these and have not had time to use them myself.
  21322. Geoffrey Probert
  21323. Broomfield, CO
  21324. This is in response to the letter from Moses Mwarari Maina of Nairobi, Kenya
  21325. which was published in the September 1991 issue of CUJ. Mr. Maina might
  21326. benefit from the book Turbo C: Memory-Resident Utilities, Screen I/O, and
  21327. Programming Techniques, written by Al Stevens and published by MIS Press in
  21328. 1987. MIS was located here in Portland; I just tracked them down. They are now
  21329. a division of Henry Holt Company, 4375 West 1980 South, Salt Lake City, UT
  21330. 84104. Their WATS number is 800-408-5233. They still publish this book.
  21331. Beginning with Chapter 4, chapter titles are: General- purpose Functions,
  21332. Screen Windows, The Windows Function Library, Context-sensitive Help Windows,
  21333. Data Entry Windows, the Windows Text Editor (aside: Al also did a series, in
  21334. DDJ I believe, on TWP: Tiny Word Processor), Windows Menus, Memory-resident
  21335. Programs, and Building Turbo C Memory-resident Programs.
  21336. Another source I can recommend from personal experience is IDC (Innovative
  21337. Data Concepts) at 122 North York Road, Suite 5, Hatboro, PA 19040. They
  21338. developed the shareware TesSeRact TSR library and purchased Mike Smedley's
  21339. superb CXL windowing/menu/data entry/general purpose C function shareware
  21340. library (it's now TCXL version 5.52). I hope this helps Mr. Maina. Kudos to
  21341. you and the rest of the CUJ team for a valuable publication
  21342. Richard B. Shepard
  21343. Thanks to you both for your sharing of the information. (KP)
  21344. Figure 1 Output if Compiled as Compact, Large or Huge
  21345. &ptr = 4062, ptr = 6207, *ptr = 4056
  21346. *ptr = 100, &ptr = 4062, ptr = 6207
  21347. ptr = 4056, *ptr = 6207, &ptr = 100
  21348.  
  21349. urn[0] = 100, &urn[0] = 4056
  21350. &urn[0] = 4056, urn[0] = 6207
  21351.  
  21352. Figure 2 Output if Compiled as Tiny, Small or Medium
  21353. &ptr = 65494, ptr = 65488, *ptr = 100
  21354. *ptr = 100, &ptr = 65494, ptr = 65488
  21355. ptr = 65488, *ptr = 100, &ptr = 65494
  21356.  
  21357. urn[0] = 100, &urn[0] = 65488
  21358. &urn[0] = 65488, urn[0] = 100
  21359.  
  21360. Listing 1
  21361. main()
  21362. {
  21363. int urn[3] = {100,200,300};
  21364. int *ptr;
  21365.  
  21366. ptr = urn;
  21367. printf("&ptr = %u, ptr = %u, *ptr = %u\n", &ptr, ptr, *ptr);
  21368. printf("*ptr = %u, &ptr = %u, ptr = %u\n", *ptr, &ptr, ptr);
  21369. printf("ptr = %u, *ptr = %u, &ptr = %u\n\n", ptr, *ptr, &ptr);
  21370.  
  21371. printf("urn[0] = %u, &urn[0] = %u\n", urn[0], &urn[0]);
  21372. printf ("&urn[0] = %u, urn[0] = %u\n\n", &urn[0], urn[0]);
  21373. getch();
  21374. }
  21375.  
  21376. /* End of File */
  21377.  
  21378.  
  21379.  
  21380. Listing 2
  21381. unsigned long la, lb, lc;
  21382. printf("Longs are %lu %lu %lu", la, lb, lc);
  21383. printf("Bad way to print longs %u %u %u", la, lb, lc);
  21384.  
  21385. /* End of File */
  21386.  
  21387.  
  21388. Listing 3
  21389. PLOAD:
  21390. DEF SEG = &HB000
  21391. BLOAD N$,0
  21392. DEF SEG
  21393. RETURN
  21394. PSAVE:
  21395. DEF SEG = &HB000
  21396. BSAVE N$,0,32767
  21397. DEF SEG
  21398. RETURN
  21399.  
  21400.  
  21401. Listing 4
  21402. #include <stdlib.h>
  21403. #include <stdio.h>
  21404. main()
  21405. {
  21406. FILE *fp;
  21407. char far *hga_buff;
  21408. unsigned bytes_written;
  21409. hga_buff = (char far *) 0xB0000000L;
  21410. if ( fp = fopen("screen.pic", "wb") == NULL )
  21411. {
  21412. puts("Error opening file."};
  21413. exit(1);
  21414. }
  21415. bytes_written = fwrite(hga_buff,1,32767,fp);
  21416. printf("%u bytes written to file.\n",bytes_written);
  21417. }
  21418.  
  21419. /* End of File */
  21420.  
  21421.  
  21422.  
  21423.  
  21424.  
  21425.  
  21426.  
  21427.  
  21428.  
  21429.  
  21430.  
  21431.  
  21432.  
  21433.  
  21434.  
  21435.  
  21436.  
  21437.  
  21438.  
  21439.  
  21440.  
  21441.  
  21442. Stepping Up To C++
  21443.  
  21444.  
  21445. Operator Overloading
  21446.  
  21447.  
  21448.  
  21449.  
  21450. Part 2
  21451.  
  21452.  
  21453.  
  21454.  
  21455. Dan Saks
  21456.  
  21457.  
  21458. Dan Saks is the owner of Saks & Associates, which offers consulting and
  21459. training in C and C++. He is secretary of the ANSI C++ standards committee,
  21460. and also contributing editor for Windows/DOS Developer's Journal. Dan recently
  21461. finished his first books, C++ Programming Guidelines, written with Thomas
  21462. Plum. You can write to him at 393 Leander Dr., Springfield, OH 45504, or
  21463. dsaks@wittenberg. edu (Internet), or call (513)324-3601.
  21464.  
  21465.  
  21466. In my last column, I introduced operator overloading in C++ (see "Operator
  21467. Overloading, Part 1," January 1992). Using operator overloading, you can
  21468. define new meanings for operator symbols applied to objects of user-defined
  21469. class types. Operator overloading enables the implementation of user-defined
  21470. types that are nearly indistinguishable from built-in types.
  21471. Last time, I used a simple class for rational numbers (fractions) to
  21472. demonstrate overloading of binary operators. In this article, I'll enhance the
  21473. rational class to make it more like a built-in type by adding mixed-mode
  21474. operations (operations combining rationals with other built-in types) and
  21475. unary operators.
  21476.  
  21477.  
  21478. Reviewing The Basics
  21479.  
  21480.  
  21481. Listing 1 shows the declaration for class rational as it was when I left off
  21482. in the first part of this series. Each rational number stores its numerator
  21483. and denominator in a pair of signed long integers. The class declares eight
  21484. overloaded binary operators: +, -, *, and /, and their corresponding
  21485. assignment operators +=, -=, *=, and /=. Using this class you can declare
  21486. objects of type rational, and perform rational arithmetic using the overloaded
  21487. operators. For example, if r1, r2, and r3 are rationals, then
  21488. r1 += r2;
  21489. adds r2 to r1 using rational ::operator +=. The statement compiles as if you
  21490. had written
  21491. r1.operator+=(r2);
  21492. Similarly,
  21493. r1 = r2 + r3;
  21494. adds r2 and r3 using rational::operator+ and then copies the result to r1
  21495. using rational::operator=. This statement compiles as if you had written
  21496. r1.operator=(r2.operator+(r3));
  21497. Note that Listing 1 doesn't provide a declaration for rational::operator=. The
  21498. compiler generates a default version of operator= if you don't write one
  21499. explicitly. The default version uses memberwise assignment. That is, it copies
  21500. each member of the right-hand operand to the corresponding member of the
  21501. left-hand operand using that member's operator=.
  21502. Listing 2 presents definitions for the rational member functions. I
  21503. implemented each arithmetic operator (e.g. +) in terms of its corresponding
  21504. assignment operator (e.g. +=). Note that the first line in the body of each
  21505. arithmetic operator is the declaration
  21506. rational result(*this);
  21507. which uses the rational copy constructor
  21508. rational::
  21509. rational (const rational &);
  21510. to initialize the local variable result with a copy of *this. (Recall that
  21511. this is the address of the object for which the member function was called.) I
  21512. did not declare the copy constructor explicitly; I let the compiler generate
  21513. it.
  21514. The member function rational::put (FILE *) prints a rational number in the
  21515. format (num/denom.) rational::simplify is a private member function that
  21516. reduces a rational number to its simplest form. (My method for reducing
  21517. fractions is admittedly too simple for an industrial-strength class, but the
  21518. fine points of fractional arithmetic are secondary to my presentation of
  21519. overloaded operators. Zeidler[1] presents a better algorithm for reducing
  21520. fractions.)
  21521. simplify uses a general-purpose library function gcd that computes the
  21522. greatest common divisor of its arguments. I presented a version of gcd in the
  21523. first part of this series, but since then I've discovered problems with the
  21524. implementation. Listing 3 contains a new version of gcd based on an algorithm
  21525. also provided by Zeidler[1]. I compiled gcd separately and placed the object
  21526. code in a linkable library called mylib. simplify accesses gcd's declaration
  21527. by including mylib.h.
  21528.  
  21529.  
  21530. Constructors As Conversions
  21531.  
  21532.  
  21533. C++, like C, provides standard conversions among the arithmetic types. These
  21534. rules simplify writing arithmetic expressions. For example, when you mix ints
  21535. and longs in the same expression, the compiler automatically promotes the ints
  21536. to longs, and does all the computations in long arithmetic. Similarly, you can
  21537. initialize a long with an int expression, such as
  21538. long n = 0;
  21539. The compiler automatically converts the integer literal 0 to a long value. You
  21540. need not write
  21541. long n = 0L;
  21542. The compiler also performs this conversion when you pass an int to a function
  21543. that expects a long argument.
  21544. Unfortunately, the rational class defined in Listing 1 does not provide
  21545. automatic conversions to or from other arithmetic types. For example, you
  21546. cannot write an expression like
  21547. rational r1, r2;
  21548. ...
  21549. r1 = r2 + 1;
  21550.  
  21551. You must write it as
  21552. r1 = r2 + rational(1, 1);
  21553. The problem is that rational::operator+ requires two rational operands, and
  21554. the literal 1 is an int, not a rational.
  21555. You could solve this problem by extending the class to include a member
  21556. function rational::operator+(long n), as shown in Listing 4. The return
  21557. statement in that function uses the constructor call rational(n, 1) to convert
  21558. long n to its equivalent rational value, n/1. Adding this member function to
  21559. the class lets you write expressions like
  21560. r1 = r2+ 1L;
  21561. It even lets you write
  21562. r1 = r1 + 1;
  21563. because the compiler promotes 1 to long before calling
  21564. rational::operator+(long).
  21565. The problem with this approach is that you must also add
  21566. rational operator+=(long);
  21567. rational operator-(long);
  21568. rational operator-=(long);
  21569. and so on, for every operator defined by the class. A class written this way
  21570. gets very big very fast, and contains lots of duplicated code.
  21571. If you also add operator=(long) to the class, you can write
  21572. rational r;
  21573. ...
  21574. r = 3;
  21575. which assigns 3/1 to r. Unfortunately, even with this additional operator, you
  21576. still can't write declarations like
  21577. rational r = 3;
  21578. Remember, this is not an assignment; it's an initialization that uses a
  21579. constructor called with one argument, as if you had written either
  21580. rational r = rational(3);
  21581. or
  21582. rational r (3);
  21583. To overcome this problem, you must add a rational constructor that accepts a
  21584. single argument of type long, defined as
  21585. rational (long n):
  21586. num(n), denom(1) {}
  21587. It turns out that this one constructor eliminates the need for all those extra
  21588. operator functions that accept a single long argument. A class constructor
  21589. with one argument acts as a rule for converting the argument type to the class
  21590. type. For example, adding the above constructor to the class definition in
  21591. Listing 1 lets you write
  21592. rational r;
  21593. ...
  21594. r = 3;
  21595. In translating the assignment, the compiler finds only one assignment operator
  21596. that it could possibly use, namely
  21597. rational &rational::
  21598. operator=(const rational &);
  21599. as generated by the compiler. The compiler promotes 3 to long and passes the
  21600. result to the rational(long) constructor to create a rational object for use
  21601. as the right-hand operand in the assignment. If the constructor is written as
  21602. an inline function, a good optimizing compiler will "compile away" any
  21603. temporary objects, and produce code equivalent to
  21604. r.num = 3;
  21605. r.denom = 1;
  21606. The compiler applies the rational(long) constructor whenever it needs to
  21607. covert the right-hand operand of a rational operator. An expression like
  21608. r /= 2;
  21609. compiles as if you had written
  21610. r.operator/=(rational(2));
  21611. and converts the 2 to the rational value 2/1 before passing it to operator/=.
  21612. Similarly,
  21613. r1 = r2 * 2;
  21614. compiles as if you had written
  21615. t1 = r2.operator+ (rational (2));
  21616. rl.operator = (t1);
  21617. where t1 is a temporary rational object added for readability.
  21618. The implicit conversion from integral types to rational lets you write code
  21619. that looks even more as if rationals were built in. However, it also lets you
  21620. inadvertently write less efficient code. For example, you can rewrite
  21621. r1 = r2 * rational(2,3) + r3;
  21622. as
  21623. r1 = r2 * 2/3 + r3;
  21624. Despite the spacing, C++'s precedence rules interpret the latter expression as
  21625. r1 = ((r2 * 2) / 3) + r3;
  21626. which compiles as
  21627. t1 = r2.operator*(rational(2));
  21628. t2 = t1.operator/(rational(3));
  21629. r1 = t2.operator+(r3);
  21630. (Again, t1 and t2 are temporary rational objects added only for readability.)
  21631. Whereas the expression rational(2,3) explicitly creates a single object whose
  21632. value is 2/3, the expression 2/3 gets regrouped into two separate rational
  21633. objects and introduces a separate call to rational::operator/.
  21634. Note that you cannot create a single rational object by simply adding
  21635. parentheses
  21636. r1 = r2 * (2/3) + r3;
  21637.  
  21638. In this case, the compiler sees that 2 and 3 are integer literals, so it
  21639. performs 2/3 using integer division. The result is an integer-valued zero,
  21640. which is converted to rational when used as the right-hand operand of
  21641. rational::operator*.
  21642. The compiler doesn't know that you intended 2/3 to be a rational constant. It
  21643. doesn't promote the operand types if they are already the same type. This
  21644. situation is no different than if r1, r2, and r3 had been float or double
  21645. variables. 2/3 grouped as such always yields zero.
  21646.  
  21647.  
  21648. Non-Member Operator Functions
  21649.  
  21650.  
  21651. When you overload operators using members functions, the compiler can apply
  21652. user-defined conversions to the right-hand argument, but not to the left. It
  21653. follows that the left operand must be a class object. Thus you can write
  21654. expressions like
  21655. r1 = r2 * 2;
  21656. but not like
  21657. r1 = 2 * r2;
  21658. because 2 is not a class object. The compiler will not take the liberty of
  21659. commuting the operands to produce
  21660. r1 = r2 * 2;
  21661. because some operations, like -- and /, are not commutative.
  21662. If you want the compiler to apply user-defined conversion to the left operand
  21663. as well as the right operand of an overloaded operator, then implement that
  21664. operator as a non-member function. For example, you move the member function
  21665. rational rational::
  21666. operator+(rational r);
  21667. outside the class declaration, and rewrite its declaration as the non-member
  21668. function
  21669. rational operator+
  21670. (rational r1, rational r2);
  21671. Notice that although the member function appears to have only one formal
  21672. argument, the non-member function has two. But a member function really has an
  21673. extra (hidden) argument -- the address of the object addressed by this. When
  21674. you rewrite the operator as a non-member function, you must add a second (or
  21675. is it a first?) explicit argument.
  21676. Changing this operator from a member to a non-member function doesn't affect
  21677. any of the calls to operator+ using infix notation. The fact is, when the
  21678. compiler sees r1 + r2, where either r1 or r2 is class type, it tries compiling
  21679. the expression as either r1.operator+(r2) or operator+(r1, r2), and uses
  21680. whichever one it finds.
  21681. If you define both forms of operator+ accepting identical argument types, then
  21682. when you write r1 + r2, the compiler produces a diagnostic that says the
  21683. expression is an ambiguous reference. If you define both forms of operator+
  21684. accepting sufficiently different argument types, the compiler selects the best
  21685. match according to an elaborate set of argument matching rules. Ellis and
  21686. Stroustrup[2] and Stroustrup[3] describe these rules in detail. I will explain
  21687. them in a future column.
  21688. Listing 5 shows a new version of the header rational.h that declares class
  21689. rational with the arithmetic operators +, -, *, and / implemented as
  21690. non-member functions. The body of each function is a one-liner that uses the
  21691. corresponding assignment operator to do the computation. For example, the body
  21692. of operator+ is simply
  21693. return r1 += r2;
  21694. The operator function doesn't need a local rational object to hold the result;
  21695. it uses its local copy of the left-hand operand for temporary storage. Since
  21696. the left operand of operator+ is passed by value, changing the value of the
  21697. formal parameter has no effect on the actual argument passed to operator+. The
  21698. arithmetic operators are so short, it's appropriate to declare them as inline
  21699. functions in the header.
  21700. When the operators were member functions, they had access to the private
  21701. members of rational objects. When you rewrite them as non-member functions
  21702. they lose their access rights, but the loss poses no problem because these
  21703. functions never exercised those rights. For example, all the real work of
  21704. operator+ is done by calling rational::operator+=, which still has all its
  21705. private access rights. operator+= manipulates the private num and denom
  21706. members of the rational objects, and returns a reference to a complete
  21707. rational object.
  21708.  
  21709.  
  21710. Using const Reference Arguments
  21711.  
  21712.  
  21713. The size of a rational object is the sum of the sizes of its data members,
  21714. namely, 2 * sizeof(long). This is rarely smaller, and often two or four times
  21715. the size of a pointer. Therefore, you may want to pass the arguments to these
  21716. operators by const reference rather than by value. (The underlying
  21717. implementation of a reference is a pointer, so passing by reference is often
  21718. cheaper than passing a large object by value.) You can certainly do this with
  21719. the member functions, e.g.
  21720. rational &operator+=(const rational &);
  21721. without changing anything else in the function definition or in any function
  21722. call. You can also do it with the second argument of the non-member functions,
  21723. e.g.,
  21724. inline rational operator+
  21725. (rational r1, const rational &r2);
  21726. However, as implemented in Listing 5, you can't change the first argument, r1,
  21727. to a const reference because the function alters r1's value. If you insist on
  21728. changing r1 to a const reference, then you must use a local rational variable
  21729. to hold the result, as shown in Listing 6, but I doubt you gain anything by
  21730. writing the function this way.
  21731.  
  21732.  
  21733. Unary Operators
  21734.  
  21735.  
  21736. Built-in arithmetic types support a number of unary operators. Rationals
  21737. should too. Listing 7 shows the class declaration in rational.h extended to
  21738. include six unary operators: +, -, prefix ++ and -- --, and postfix ++ and --
  21739. --.
  21740. In general, you overload unary operators using either a member function with
  21741. no arguments or a non-member function with one argument. (The exceptions are
  21742. postfix ++ and -- --, explained later.) For example, as a member function, you
  21743. declare rational unary + as
  21744. rational rational::operator+();
  21745. As a non-member function, declare it as either
  21746. rational operator+(rational);
  21747. or
  21748. rational operator+(const rational &);
  21749. In Listing 7, I used the member function notation.
  21750. The implementation of unary + is trivial, so I simply defined it inside the
  21751. class definition. Hence, the function is inline by default. Unary + simply
  21752. returns the value of its operand.
  21753. I try to make overloaded operators for user-defined types act as much as
  21754. possible as they do for predefined types. Unary + applied to predefined types
  21755. returns an rvalue, not an lvalue. The return value of a function is an rvalue
  21756. unless it returns a reference. Therefore, I made the return value of
  21757. rational's unary + a rational, rather than a rational &.
  21758. The implementation of unary - is a little more difficult. The function
  21759. definition appears in Listing 8 along with the other non-inline unary operator
  21760. functions. The return value is simply the value of the operand with its
  21761. numerator negated. However, you must be careful to leave the operand
  21762. unchanged. If the body of the function were simply
  21763. num = -num;
  21764. return *this;
  21765. it would negate the operand itself, and you would get surprising behavior. For
  21766. example, if negation changed its operand, then the second statement in
  21767. r1 = rational (1, 1); // r1 = 1/1
  21768. r2 = -r1; // r2 = -1/1
  21769.  
  21770. would also change r1 to -1/1. To avoid this odd behavior, rational unary -
  21771. computes the negation in a local variable.
  21772. Early versions of C++ (those compatible with AT&T cfront up to release 2.0)
  21773. did not distinguish between prefix and postfix applications of overloaded ++
  21774. and -- --. That is, for a class like rational you could write a single
  21775. function operator++ (either as a member or non-member) and invoke that
  21776. function as either ++r or r++. There was no way to get ++r and r++ to behave
  21777. differently as they do for primitive types.
  21778. Newer C++ compilers let you overload the prefix and postfix operators
  21779. separately. You declare the prefix operator++ much as you do any other unary
  21780. operator using a member function declared as
  21781. rational rational::operator++();
  21782. or a non-member function declared as
  21783. rational operator++(rational);
  21784. You declare the postfix operator++ with an additional argument of type int, as
  21785. a member function
  21786. rational rational::operator++(int);
  21787. or as a non-member function
  21788. rational operator++(rational, int);
  21789. For a rational r, the expression ++r compiles as the call
  21790. r.operator++()
  21791. and the expression r++ compiles as
  21792. r.operator++(0)
  21793. You can use an explicit function call to the postfix operator to pass a
  21794. non-zero value as the int argument, but I haven't yet seen a reason to do
  21795. this. In practice, the int argument is a dummy argument that distinguishes the
  21796. postfix from the prefix operator.
  21797. The implementation of the prefix operators are very simple. For predefined
  21798. types, ++r is defined to be r+=1, and that's exactly what the overloaded
  21799. prefix rational ++ does. The postfix rational++ must do a little more work to
  21800. set aside a copy of the prior value of the operand while it increments the
  21801. operand.
  21802. For predefined types, the operand of prefix and postfix ++ and -- -- must be a
  21803. modifiable lvalue, but the return type is not an lvalue. This means, for
  21804. example, that ++2 and K++ (where K is a const variable) are invalid. The
  21805. overloaded rational operators should try to preserve this behavior.
  21806. Unfortunately, as a member function, operator++ and operator -- -- will accept
  21807. operands other than modifiable lvalues. For example, the member function
  21808. accepts a call like ++rational(1, 2). However, you should be able to prevent
  21809. such expressions by defining operator++ as the non-member function
  21810. inline
  21811. rational operator++(rational &r)
  21812. {
  21813. return r += 1;
  21814. }
  21815. That is, the rational argument is passed as a non-const reference. Since a
  21816. rational & argument can only be bound to a modifiable rational object (an
  21817. lvalue), this function definition should not accept a call like ++rational(1,
  21818. 2). Therefore, I recommend overloading ++ and -- -- this way.
  21819. I say that this should be able to prevent non-modifiable lvalue operands
  21820. because that is my understanding of the rules for references defined by the
  21821. Annotated C++ Reference Manual (the ARM)[2], and by the C++ draft standard.
  21822. However, as I explained in an earlier column on references (see "Reference
  21823. Types", CUJ, September 1991) some current compilers still bind non-const
  21824. references to a constant expression by binding the reference to a temporary
  21825. initialized by the value of the expression. Since I haven't had the
  21826. opportunity to use a compiler that agrees with my interpretation, there's room
  21827. for some debate about my recommendation.
  21828. References
  21829. [1] Zeidler, Steve, "Doing Fractions in C++", The C Users Journal, Vol. 9, No.
  21830. 11, Nov. 1991.
  21831. [2] Ellis, Margaret A. and Bjarne Stroustrup, The Annotated C++ Reference
  21832. Manual. Addison-Wesley, Reading, MA, 1990.
  21833. [3] Stroustrup, Bjarne, The C++ Programming Language, 2nd. ed. Addison-Wesley,
  21834. Reading, MA, 1991.
  21835.  
  21836. Listing 1 (rational.h)
  21837. #include <stdio.h>
  21838.  
  21839. class rational
  21840. {
  21841. public:
  21842. rational (){ }
  21843. rational(long n, long d) : num(n), denom(d) { }
  21844. rational operator+(rational);
  21845. rational operator-(rational);
  21846. rational operator*(rational);
  21847. rational operator/(rational);
  21848. rational &operator+=(rational);
  21849. rational &operator-=(rational);
  21850. rational &operator*=(rational);
  21851. rational &operator/=(rational);
  21852. void put(FILE *);
  21853. private:
  21854. long num, denom;
  21855. void simplify();
  21856. };
  21857.  
  21858. /* End of File */
  21859.  
  21860.  
  21861. Listing 2 (rational.cpp)
  21862. #include "mylib.h"
  21863. #include "rat1.h"
  21864.  
  21865. rational &rational::operator+=(rational r)
  21866.  
  21867. {
  21868. num = num * r.denom + r.num * denom;
  21869. denom *= r.denom;
  21870. simplify();
  21871. return *this;
  21872. }
  21873.  
  21874. rational &rational::operator-=(rational r)
  21875. {
  21876. num = num * r.denom - r.num * denom;
  21877. denom *= r.denom;
  21878. simplify();
  21879. return *this;
  21880. }
  21881.  
  21882. rational &rational::operator*=(rational r)
  21883. {
  21884. num *= r.num;
  21885. denom *= r.denom;
  21886. simplify();
  21887. return *this;
  21888. }
  21889.  
  21890. rational &rational::operator/=(rational r)
  21891. {
  21892. num *= r.denom;
  21893. denom *= r.num;
  21894. simplify();
  21895. return *this;
  21896. }
  21897.  
  21898. rational rational::operator+(rational r)
  21899. {
  21900. rational result(*this);
  21901. return result += r;
  21902. }
  21903.  
  21904. rational rational::operator-(rational r)
  21905. {
  21906. rational result(*this);
  21907. return result -= r;
  21908. }
  21909.  
  21910. rational rational::operator*(rational r)
  21911. {
  21912. rational result(*this);
  21913. return result *= r;
  21914. }
  21915.  
  21916. rational rational::operator/(rational r)
  21917. {
  21918. rational result(*this);
  21919. return result /= r;
  21920. }
  21921.  
  21922. void rational::put(FILE *f)
  21923. {
  21924. fprintf(f,"(%ld/%ld)", num, denom);
  21925. }
  21926.  
  21927.  
  21928. void rational::simplify()
  21929. {
  21930. long x = gcd(num, denom);
  21931. num /= x;
  21932. denom /= x;
  21933. }
  21934.  
  21935. // End of File
  21936.  
  21937.  
  21938. Listing 3 (gcd.cpp)
  21939. #include <stdlib.h>
  21940. #include "mylib.h"
  21941.  
  21942. long gcd(long x, long y)
  21943. {
  21944. ldiv_t r;
  21945. x= labs(x);
  21946. if ((y = labs (y)) == 0)
  21947. return x;
  21948. do
  21949. {
  21950. r = ldiv(x, y);
  21951. x = y;
  21952. }
  21953. while ((y = r.rem) !=0);
  21954. return x;
  21955. }
  21956.  
  21957. // End of File
  21958.  
  21959.  
  21960. Listing 4
  21961. rational::operator+(long n)
  21962. {
  21963. rational result(*this);
  21964. return result += rational(n, 1);
  21965. }
  21966.  
  21967. // End of File
  21968.  
  21969.  
  21970. Listing 5 (rational.h)
  21971. #include <stdio.h>
  21972.  
  21973. class rational
  21974. {
  21975. public:
  21976. rational() { }
  21977. rational(long n) : num(n), denom(1) { }
  21978. rational(long n, long d) : num(n), denom(d) { }
  21979. rational &operator+=(rational);
  21980. rational &operator-=(rational);
  21981. rational &operator*=(rational);
  21982. rational &operator/=(rational);
  21983. void put(FILE *);
  21984. private:
  21985. long num, denom;
  21986.  
  21987. void simplify();
  21988. };
  21989.  
  21990. inline rational
  21991. operator+(rational r1, rational r2)
  21992. {
  21993. return r1 += r2;
  21994. }
  21995.  
  21996. inline rational
  21997. operator-(rational r1, rational r2)
  21998. {
  21999. return r1 -= r2;
  22000. }
  22001.  
  22002. inline rational
  22003. operator*(rational r1, rational r2)
  22004. {
  22005. return r1 *= r2;
  22006. }
  22007.  
  22008. inline rational
  22009. operator/(rational r1, rational r2)
  22010. {
  22011. return r1 /= r2;
  22012. }
  22013.  
  22014. /* End of File */
  22015.  
  22016.  
  22017. Listing 6
  22018. inline rational
  22019. operator+(const rational &r1, const rational &r2)
  22020. {
  22021. rational result(r1);
  22022. return result += r2;
  22023. }
  22024.  
  22025. // End of File
  22026.  
  22027.  
  22028. Listing 7 (rational.h)
  22029. #include <stdio.h>
  22030.  
  22031. class rational
  22032. {
  22033. public:
  22034. rational() { }
  22035. rational(long n) : num(n), denom(1) { }
  22036. rational(long n, long d) : num(n), denom(d) { }
  22037. rational &operator+=(rational r);
  22038. rational &operator-=(rational r);
  22039. rational &operator*=(rational r);
  22040. rational &operator/=(rational r);
  22041. rational operator+() { return *this; }
  22042. rational operator-();
  22043. rational operator++() { return *this += 1; }
  22044. rational operator--() { return *this -= 1; }
  22045. rational operator++(int); // postfix ++
  22046.  
  22047. rational operator--(int); // postfix --
  22048. void put(FILE *);
  22049. private:
  22050. long num, denom;
  22051. void simplify();
  22052. };
  22053.  
  22054. // ... the rest of rational.h as in Listing 5
  22055.  
  22056. /* End of File */
  22057.  
  22058.  
  22059. Listing 8 (rational.cpp)
  22060. #include "mylib.h"
  22061. #include "rational.h"
  22062.  
  22063. rational rational::operator-()
  22064. {
  22065. rational result(*this);
  22066. result.num = -result.num;
  22067. return result;
  22068. }
  22069.  
  22070. rational rational::operator++(int)
  22071. {
  22072. rational result(*this);
  22073. *this += 1;
  22074. return result;
  22075. }
  22076.  
  22077. rational rational::operator--(int)
  22078. {
  22079. rational result(*this);
  22080. *this -= 1;
  22081. return result;
  22082. }
  22083.  
  22084. // ... the rest of rational.cpp as in Listing 2
  22085.  
  22086. // End of File
  22087.  
  22088.  
  22089.  
  22090.  
  22091.  
  22092.  
  22093.  
  22094.  
  22095.  
  22096.  
  22097.  
  22098.  
  22099.  
  22100.  
  22101.  
  22102.  
  22103.  
  22104.  
  22105.  
  22106.  
  22107.  
  22108.  
  22109.  
  22110. On the Networks
  22111.  
  22112.  
  22113. Is Everything Miscellaneous?
  22114.  
  22115.  
  22116.  
  22117.  
  22118. Sydney S. Weinstein
  22119.  
  22120.  
  22121. Sydney S. Weinstein, CDP, CCP is a consultant, columnist, author, and
  22122. president of Datacomp Systems, Inc., a consulting and contract programming
  22123. firm specializing in databases, data presentation and windowing, transaction
  22124. processing, networking, testing and test suites, and device management for
  22125. UNIX and MS-DOS. He can be contacted care of Datacomp Systems, Inc., 3837
  22126. Byron Road, Huntingdon Valley, PA 19006-2320 or via electronic mail on the
  22127. Internet/Usenet mailbox syd@DSI.COM (dsinc!syd for those who cannot do
  22128. Internet addressing).
  22129.  
  22130.  
  22131. It appears that everyone has decided that they want the early appearance of
  22132. their postings in comp.sources.misc over the testing of them in the other
  22133. groups. During the past two months I counted the postings I saved, so I could
  22134. mention them in this column. There were 0.5MB in comp.sources.games, 1.2MB in
  22135. comp.sources.unix, 1.3MB in comp.sources.reviewed, and a whopping 16MB in
  22136. comp.sources.misc. Now, this is only what I saved, which for all of the above
  22137. except the misc group, is everything. In misc, I do not keep anything that is
  22138. not written in C, that is totally specific to a single machine, or is
  22139. shareware that requires a registration fee.
  22140. There is plenty to talk about, but it all falls into the misc group it seems.
  22141.  
  22142.  
  22143. Rich?, Part N
  22144.  
  22145.  
  22146. Only one posting showed up in comp.sources.unix this time. Chris Lewis
  22147. <psroff-request@ferret.ocunix.on.ca> posted a major package called psroff in
  22148. comp.sources.unix this time. It will be useful for those UNIX systems stuck
  22149. with the older C/A/T based troff. The program troff accepts text input and
  22150. outputs phototypesetter commands. Originally, it was designed to drive a
  22151. particular typesetter in use at Bell Laboratories. Bell used this C/A/T
  22152. typesetter to set most of its documents in the late 1970s and early 1980s.
  22153. Now, with laser printers based on Postscript or HP/PCL, output from troff
  22154. could potentially become unusable. However, psroff can convert the original
  22155. C/A/T troff output into Postscript or HP/PCL for use in Postscript printers or
  22156. the HP LaserJet/DeskJet family of printers. It upgrades the old troff, with
  22157. few limitations, to be equivalent to the more modern device independent troff
  22158. (ditroff). Not only does it handle the old troff, but it can also handle
  22159. converting ditroff output into Postscript or HP LaserJet/DeskJet output.
  22160. psroff v3.0, , was posted on September 26, 1991 as Volume 24, Issues 96-114
  22161. with patches 1 through 4 as Issues 115-118.
  22162.  
  22163.  
  22164. Hold Those Presses!
  22165.  
  22166.  
  22167. NEWS FLASH: This column is mostly written, and I was just checking the thing
  22168. over when a plea crossed my desk. Paul Vixie, the postmaster at Dec Western
  22169. Research Laboratories posted a request to news.admin stating that he was
  22170. getting many copies of the postings to comp.sources.unix as its primary
  22171. moderator and would people check their active files as some had it marked as
  22172. unmoderated. Well, I don't remember seeing any change in moderator announced
  22173. (although there have been a lot of "Dump the currently not producing
  22174. moderator" sentiment brewing for a while). So this was a surprise. Perhaps,
  22175. shortly, some postings will resume in this group.
  22176.  
  22177.  
  22178. Reviews Also Almost Empty
  22179.  
  22180.  
  22181. New postings to comp.sources.reviewed this time consisted of patches to Chip
  22182. Salzenberg's Deliver program, reported on in the December 1991 issue, plus
  22183. three new programs.
  22184. Deliver is a system to handle incoming electronic mail. It can forward the
  22185. mail based on content, store it in a set of folders, reply with requested
  22186. information to the sender, or anything that can be described as a shell
  22187. script. Patches 7 and 8 to version 2.1 were posted as Volume 1 Issue 15 and
  22188. Volume 1 Issue 40 respectively.
  22189. New postings consisted of mawk, an interpreter for the awk language. awk is a
  22190. utility language provided with UNIX systems named after the first initial of
  22191. each of its authors' last names. The awk language is useful for text
  22192. conversions, general report writing, and almost any task that needs to take
  22193. text in and convert/process it into different text on the way out. This
  22194. interpreter is based on the version of awk as defined in the 1988 book, also
  22195. known as the "New awk." mawk is faster than either the new or old awks and has
  22196. been ported to many flavors of UNIX as well as MS-DOS. mawk was contributed by
  22197. Mike Brennan <brennan@boeing.com> for Volume 1, Issues 16-30.
  22198. The SuperServer program, posted as Volume 1, Issues 1-3 was update with a new
  22199. release. SuperServer allows any program to appear as a TCP/IP service. It does
  22200. so without requiring any special programming. Even shell scripts can be made
  22201. into services and it aids in the debugging of network servers. Note:
  22202. SuperServer does require the BSD Socket calls, including the select system
  22203. call. New features include removing the need for a separate supersrv program.
  22204. The first server started now acts as the master server. In addition, the
  22205. master and its clients now can use UNIX domain sockets instead of IP domain
  22206. sockets. This can save CPU time by not requiring network wide byte ordering to
  22207. be used (optional). Version 1.5 was contributed by Steven Grimm
  22208. <Steven.Grimm@eng.sun.com> for Volume 1 Issues 31-34.
  22209. Lastly, is Ajay Shah's <ajayshah%monty@rand.org> ols, a linear regression
  22210. tool. ols does much of what a normal statistics package does, only by way of
  22211. linear regressions. While the package provides a very useful set of tools to
  22212. fit data, it is designed for those that already know and understand linear
  22213. regression theory. It was posted in Volume 1 as Issues 35-39.
  22214.  
  22215.  
  22216. misc Overflows My Disk
  22217.  
  22218.  
  22219. So much was posted to comp.sources.misc, I had to clean out some of the
  22220. directory prior to the end of the two months just to have room. With so many
  22221. new packages posted, I can only go into selections from the over seventy
  22222. different packages that I saved to comment on.
  22223. In December, I reported on crack v3.2a. It turns out that v3.2a was an
  22224. impostor version posted as v3.2b by someone else. To correct this, an updated
  22225. version, 3.3c was posted as Volume 23, Issues 1-5. But, hold, on, Alec David
  22226. Muffett <aem@aber.ac.uk> re-thought through the problem, and rewrote it
  22227. entirely. crack v4.0a was posted as Volume 25, Issues 5-9. crack is a program
  22228. used by system administrators under UNIX to check that users did not pick
  22229. easily compromised passwords. This new version supports network wide load
  22230. leveling, a programmable dictionary generator, better handling of GECOS-based
  22231. passwords, faster fcrypt algorithm, a simpler user interface and better
  22232. portability.
  22233. unproto, mentioned in December's previews from alt.sources has been released
  22234. by Wietse Venema <wietse@wzv.win.tue.nl> for Volume 23, Issues 12 and 13. It
  22235. allows K&R C compilers to compile ANSI C programs by converting the ANSI
  22236. constructs back to K&R constructs after the preprocessor stage. It can handle
  22237. function headings, function pointer type declarations and casts, function type
  22238. declarations and combinations of those items.
  22239. Another posting previewed in December provided changes required to port GCC
  22240. 1.40, GAS 1.38.1, and GDB 3.5 from the GNU project to SCO XENIX. Steve Blezard
  22241. <Steve.Bleazard@robobar.co.uk> contributed these as Volume 23, Issue 28. This
  22242. port works on SCO XENIX 386 and produces files in the native Microsoft/Intel
  22243. OMF format. The GNU binutils are not used, so the port is compatible with the
  22244. standard library files.
  22245. One of the backup tools used for UNIX is cpio (cp in/out, cp is the UNIX copy
  22246. command). cpio has its problems in its original form, including lack of
  22247. multiple volume support (the backup must fit on one tape) and no recovery of
  22248. tape read errors. One error and the entire archive is lost. afio, a freely
  22249. distributable version of cpio, was contributed as v2.2 by Jeff Buhrt
  22250. <prslnk!buhrt> for Volume 23, Issues 33 and 34. It supports floppy disks as
  22251. well as tapes, and allows for compression/uncompression on the fly,
  22252. verification of floppies and restart at any volume break. It can also somewhat
  22253. gracefully handle input data corruption.
  22254. One of the larger postings was an updated release of the Extended Portable
  22255. Bitmap Toolkit (PBMPLUS). In the June 1990 issue I extensively reviewed this
  22256. toolset. It is used to convert various image formats to and from a portable
  22257. format. It also includes many tools for manipulating the images once they are
  22258. in this portable format. The package include four parts: PBM for bitmaps (1
  22259. bit/pixel), PGM for graymaps (grayscale images), PPM for pixmaps (full color
  22260. images), and PNM for context independent manipulations of any of the three
  22261. internal formats. The tools for manipulation include smoothing, scaling,
  22262. inversion, rotation, color map compression, and combining multiple images.
  22263. This is the 27sep91 distribution and was contributed by Jef Poskanzer as
  22264. Volume 23, Issues 36-59 with patch05oct91 issued as Volume 23, Issue 60 and
  22265. patch30oct91 as Volume 25, Issue 33. The patches were mostly bug fixes.
  22266. The follow-on operating system product from the original authors of UNIX is
  22267. Plan 9. It includes a command interpreter that is very heavily based on the C
  22268. programming language. This shell is called rc. Byron Rakitzis
  22269. <byron@archone.tamu.edu> has written his own implementation of this shell and
  22270. contributed version 1.2 for Volume 23, Issues 61-66. It is reasonably small
  22271. and fast, especially when compared to the current do-everything shells in use.
  22272. It is useful for both interactive terminals/windows and command scripts.
  22273. On the other hand, an update to one of those do-everything shells was also
  22274. posted this time. Paul Falstad <pfalstad@phoeniz.princeton.edu> contributed
  22275. v2.1 of his zsh shell for Volume 24, Issues 1-19. zsh has most of the features
  22276. of tcsh, ksh, and bash plus a few more. It is mostly a ksh syntax style shell,
  22277. but it does allow most of the csh syntax. It includes many bells and whistles,
  22278. and provides a new document An Introduction to the Z Shell precompiled into
  22279. Post-Script.
  22280. And lastly, for those that use the Korn shell most everywhere, but don't have
  22281. it on all their systems, Simon J. Gerraty has submitted PDksh, a public domain
  22282. work-alike to the Korn shell (ksh). While it is not 100% compatible with ksh,
  22283. Simon uses it daily on his Sun systems and does not notice the difference
  22284. between it and ksh88.PDksh is Volume 25, Issues 47-55.
  22285. In October I also reported on the generic unzip utility posted by the Info-ZIP
  22286. Workgroup. This month the companion zip utility was contributed for Volume 23,
  22287. Issues 88-96. Info-ZIP <Info-ZIP@valeria.cs.ucla.edu> develops a complete
  22288. archive library system that can handle creating and unpacking compressed file
  22289. archives. These archives are compatible with the PKware ZIP archives.
  22290. Elber Gershon <gershon%gr@cs.utah.edu> has released v3.0 of his Gnuplot
  22291. package for Volume 24, Issues 23-48. This package provides a command-line
  22292. driver interactive plotting utility for UNIX, MS-DOS, and VMS platforms. It
  22293. supports many different types of terminals, plotters and printers and is
  22294. easily extensible to include new devices. Enhancements since v2.0 include
  22295. surface plots, errorbar plots, a rewrite of the Post-Script driver that now
  22296. also supports color PostScript and many bug fixes.
  22297. For those running mail servers or mailing lists, Stephen R. van den Berg
  22298. <berg@messua.informatik.rwth-aachen.de> has updated his procmail program to
  22299. v2.31. Posted as Volume 25, Issues 1-4, procmail can be used to sort incoming
  22300. mail, preprocess incoming mail, or implement mailing lists or servers.
  22301. procmail is small, relatively easy to install, and very configurable.
  22302. Also from the preview section of a prior column, Alan Saunders <tharr!alan>
  22303. has cleaned up and contributed his QBATCH queued batch processing software for
  22304. UNIX for Volume 25, Issues 20-25. UNIX does support background processing, and
  22305. it has the atrun command to allow jobs to be run at later times. However there
  22306. is no queueing mechanism to prevent them all from running at once. QBATCH
  22307. provides this queueing mechanism to prevent any swamping and to increase
  22308. overall throughput. It is a cleanup and rewrite from the earlier preview and
  22309. includes new documentation. Patch 1 was issued as Volume 25, Issues 58 and 59
  22310. to fix some typo-graphicals and bugs plus a few small enhancements.
  22311. If you have an alphanumeric pager (beeper) and wanted to allow your computer
  22312. to send it messages, it's easy enough. All you need to do is talk the IXO
  22313. protocol. Tom Limoncelli <tal@warren.mentorg.com> submitted ixobeeper for
  22314. Volume 25, Issue 43. It enables UNIX systems to send messages to pagers. It
  22315. supports command line or stdin for its messages and can tee the input into
  22316. stdout for a filter effect.
  22317.  
  22318. The Tool Command Language, TCL v6.1, was submitted for John Ousterhout by Karl
  22319. Lehenbauer <karl@neosoft.com> for Volume 25, Issues 69-101. This large posting
  22320. is the complete source and documentation for TCL, an embeddable tool command
  22321. language created by John Ousterhout. The distribution includes the original
  22322. paper from the USENIX Winter 1990 conference that described v3.0 of TCL as
  22323. well as current documentation and a test suite. Extensions to TCL, tclx v6.1a
  22324. were also posted at the same time as Volume 26, Issues 1-23. In addition,
  22325. these extensions also provide online help to the original TCL.
  22326. Lastly, beav, the binary file editor from Peter Reilley <pvr@wang.com> was
  22327. released at vl.32. Posted as Volume 26, Issues 37-45, beav supports editing
  22328. binary files interactively. It can not only search and change bytes in place,
  22329. it can also insert and delete bytes, of course changing the length of the
  22330. file. beav is very portable and runs under UNIX, DOS, and AmigaDOS.
  22331. Of course, there were also patches issued in the group. The USENET freely
  22332. distributable spreadsheet, sc was updated to v6.19 by patch 3, submitted by
  22333. Jeff Buhrt as Volume 23, Issue 35. This fixed some null pointer problems and
  22334. added some minor features.
  22335. David Skoll <dfs@doe.carleton.ca> has issued patch 3 for his remind package as
  22336. Volume 22, Issue 102. remind can notify you of events, appointments, or other
  22337. things you feel you should be reminded about via mail or screen notices. Patch
  22338. 4 adds no features, it just tidies up the code and makefile.
  22339. The rayshade construction group <rayshade-request@cs.princeton.edu> has
  22340. released a patch to their large graphics package reported on in the October
  22341. 1991 issue. Patch 1, listed as a "HIGH" priority patch, was posted as Volume
  22342. 23, Issue 75. It fixes many problems and adds transform, window, crop,
  22343. spotlights, and transparency support. It also speeds up several of the
  22344. processes.
  22345. Larry Wall <lwall@netlabs.com> finally issued the long awaited "Patch 11" to
  22346. Perl v4.0. Many times in the past I have sung the praises of Perl. This patch
  22347. (which runs over many parts, but is really one patch) fixes many of the
  22348. outstanding bugs in v4 and also adds a few features. This patch was posted in
  22349. two parts, patches 11 to 18 are the main patch, and patch 19 is a cleanup of
  22350. problems caused by patches 11-18. Posted as Volume 25, Issues 60-68. New
  22351. features include dbz support, calling back to Perl from C routines that were
  22352. called from Perl (not fully calling Perl from C yet...) and several new
  22353. library routines.
  22354.  
  22355.  
  22356. Games Still Quiet
  22357.  
  22358.  
  22359. Only four postings in comp.sources.games, so here is each of them.
  22360. George Sicherman <gls@windmill.att.com> contributed conn4 for Volume 12, Issue
  22361. 100. It plays games of Connect Four with you using only a CRT that supports
  22362. cursor movement.
  22363. Metroid in Volume 12, Issue 99 from Ralph Betza <ssiny!gnhmon> allows for
  22364. cracking Nintendo passwords for the game Metroid. These passwords allow access
  22365. to some of the undocumented features of the game.
  22366. Don Dodson <ata@sag.cc.purdue.edu> contributed a version of Othello that only
  22367. uses curses and termcap for Volume 12, Issue 98. This is a small compact
  22368. version of the game as the complete posting is only 817 lines.
  22369. Lastly a patch to Tim Stoehr's rogue clone to allow it to be used with VMS was
  22370. posted by Mike Zraly <mzraly@ldbvax.dnet.lotus.com> as patch 3 to the rogue
  22371. package in Volume 12, Issue 97. It adds all the programs and files necessary
  22372. to port it from UNIX to Vax VMS.
  22373.  
  22374.  
  22375. Previews from alt.sources
  22376.  
  22377.  
  22378. As ususal, alt.sources is being used for testing out and checking things that
  22379. will later be posted to the moderated groups. This is as it should be. So here
  22380. are the current previews, along with their date of posting.
  22381. With computer generated faxes becoming more popular, Klaus Schallhorn
  22382. <cnix!klaus> posted a program to convert HP LaserJet output generated from
  22383. almost any program into a PBM file scaled for use as a low or high resolution
  22384. fax image. He includes integration of the package into faxpax. Posted on
  22385. August 29, 1991 in four parts it also includes the bitmaps needed for the HP
  22386. built-in fonts.
  22387. A game to help improve typing skills, loosely based on the Space Invaders type
  22388. games was posted by Larry Moss on October 3, 1991 in one part. It uses falling
  22389. letters where the proper key must be pressed to defend against the letter. He
  22390. added some new features including a bonus round and has made it more portable
  22391. to various systems than the last version.
  22392. Jan-Piet Mens <jpm@logix.de> submitted editbuf, a routine to be used with the
  22393. curses package to allow the user to edit an alphanumeric string. It supports
  22394. using the function keys/cursor keys to move around the current field and edit
  22395. the string. This subroutine for use with curses(3X) was posted on October 17,
  22396. 1991 in one part.
  22397. Dan Bernstein <brnstnd@kramden.acf.nyu.edu> posted his client/server tool set.
  22398. It is a set of three client-server suites that support different protocols all
  22399. with the same well define interface. It allows writing programs that work the
  22400. same way independent of the underlying protocol and will aid in portability of
  22401. the programs that use it. It is UCSPI compliant (UNIX Client/Server Program
  22402. Interface) which will help with future portability issues as well.
  22403. clientservv0.80 was posted on October 21, 1991.
  22404. Those running BBS systems under UNIX may want to look for UNIX-Chat v2.10, a
  22405. multiuser chat utility, designed for UNIX System V. It does not require
  22406. sockets, but instead works with standard UNIX IPC calls. It supports multiple
  22407. conferences at one, and no limit to the number of users in any of the
  22408. conferences. It can use any terminal type, including "dumb" terminals. It was
  22409. posted in seven parts on November 16, 1991 by Marc Laukien. The first part is
  22410. the introduction, the program is the next three parts, followed by three
  22411. patches.
  22412. Now that there is a reason to look for a UNIX BBS, Ken MacLeod
  22413. <unidel@bitsko.slc.ut.us> posted the Unidel UNIX BBS system. It supports
  22414. USENET news, UNIX mail, looks like a Citadel room-based BBS system, secure
  22415. shell access, external editors, file-transfer, chat/talk, CB programs, and it
  22416. is freely distributable. It was posted on November 17, 1991 in seven parts.
  22417. On a totally different note, Lance Norskog <thinman@netcom.com> has posted
  22418. SoundKit, a set of tools to manipulate sound samples in various different
  22419. formats. This package does for sound files what the PBMPLUS package did for
  22420. graphic files. It allows for conversion between many common formats, and for
  22421. simple manipulations on the sound clips. It was posted on November 19, 1991 in
  22422. four parts.
  22423.  
  22424.  
  22425.  
  22426.  
  22427.  
  22428.  
  22429.  
  22430.  
  22431.  
  22432.  
  22433.  
  22434.  
  22435.  
  22436.  
  22437.  
  22438.  
  22439.  
  22440.  
  22441.  
  22442.  
  22443.  
  22444.  
  22445.  
  22446.  
  22447.  
  22448.  
  22449.  
  22450.  
  22451.  
  22452.  
  22453.  
  22454.  
  22455.  
  22456.  
  22457. Illustrated C
  22458.  
  22459.  
  22460. A Portable Menu Compiler, Part 2: The cmenu Translator
  22461.  
  22462.  
  22463.  
  22464.  
  22465. Leor Zolman
  22466.  
  22467.  
  22468. Leor Zolman has been involved with microcomputer programming for 15 years. He
  22469. is the author of BDS C, the first C compiler targeted exclusively for personal
  22470. computers. Leor's first book, Illustrated C, is now available from R&D
  22471. Publications, Inc. Leor and his family live in Lawrence, KS.
  22472.  
  22473.  
  22474. This is the second installment in a series of columns describing the CMENU
  22475. menu compiler system. Last time I introduced the CMENU specification
  22476. language's syntax, and described the data structures used by the cmenu
  22477. translator (pre-compiler). This month I'll present the entire procedural
  22478. section of the cmenu program.
  22479.  
  22480.  
  22481. Doing Files
  22482.  
  22483.  
  22484. cmenu's main function passes the name of each file to be processed, in
  22485. sequence, to the dofile ( ) function.
  22486. dofile( ) (beginning at line 40 of Listing 1) processes a cmenu specification
  22487. file from start to finish. The first few lines chop off the .mnu extension in
  22488. case the user included it in the filename. Two copies are then made of the
  22489. base filename. One (src_name) gets the source file extension appended onto it,
  22490. and the other (obj_name) gets the object file extension.
  22491. If opening the source file succeeds, lines 62-65 initialize some global modes
  22492. and counters. The file is entirely processed within the token loop described
  22493. earlier.
  22494. After an EOF is encountered and the main loop terminates, the file is closed
  22495. and several checks for possible error conditions are performed. In line 85 we
  22496. make sure that at least one menu was defined, or there wouldn't be much point
  22497. in writing an output file.
  22498. Lines 88-93 check if the last menu terminated correctly. If the value of
  22499. global in_menu is TRUE, then there was an endmenu keyword missing. At this
  22500. point, we also call the itemcheck( ) function to see if the last item in the
  22501. last menu was similarly incomplete. (An item isn't complete until both text
  22502. and action clauses for that item have been processed.)
  22503. The last piece of error checking is performed in lines 95-101, a test for
  22504. unresolved forward menu references. The MInfo array is scanned for entries
  22505. having a Processed flag still set to FALSE. Menu entries are created as soon
  22506. as they are first referenced (unlike item entries, for the reasons given
  22507. above). It is, therefore, possible that a menu entry referenced in an lmenu
  22508. statement was never defined or the identifier was misspelled somewhere. Either
  22509. way we get an unresolved menu reference.
  22510. Finally, if there weren't any fatal errors encountered, the object file for
  22511. the current menu is written to disk and dofile( ) returns.
  22512.  
  22513.  
  22514. Utilities, Utilities
  22515.  
  22516.  
  22517. The remaining functions in cmenu1.c provide support for creating entries in
  22518. the menu and item info tables, searching for those entries by label name,
  22519. checking for item completeness, writing the output file, string matching, and
  22520. reporting errors and warnings.
  22521. Menu info table management is performed by create_menu( ) and find_menu( ).
  22522. Because MENU structures are stored directly, there is no need to worry about
  22523. memory allocation when creating a new MINFO entry. create_menu( ) defines a
  22524. local MINFO structure, initializes it appropriately, and returns it by value
  22525. to the calling routine. To find a given MINFO entry, find_menu () iterates
  22526. through the MInfo array, comparing the name of each registered entry with the
  22527. name string supplied. When a match occurs, a pointer to the structure having
  22528. the matching name is returned.
  22529. The item info table is managed by create_item( ) and find_item( ). find_item(
  22530. ) works like find_menu( ). Because IINFO structures are stored by reference
  22531. (to reduce memory requirements), create_item( ) must allocate a memory block
  22532. for the new IINFO structure and return a pointer to that block. Provided there
  22533. wasn't any problem with obtaining the memory, both the IINFO structure and its
  22534. member INFO structure get initialized with all the default values appropriate
  22535. to an unprocessed item entry.
  22536. The itemcheck( ) function is called from a few places in cmenu to make sure
  22537. both required portions of an item definition, the text and an action clause,
  22538. have been specified.
  22539. When a menu specification file has been completely processed and no fatal
  22540. errors occurred, the write_file( ) function is called to create the output
  22541. file on disk. After creating the output filename and opening the file for
  22542. writing (in binary mode), the first thing write_file( ) actually writes is the
  22543. value of the global menu count, n_menus. Each menu is then written to the file
  22544. in a format consisting of the MENU structure first, then each associated ITEM
  22545. structure. There is no need to write an explicit item count for each menu
  22546. because that information is already part of the MENU structure, and rmenu can
  22547. obtain that value dynamically when the .mnc file is loading for execution. To
  22548. insure portability across machines with different storage requirements for
  22549. various variable types, all reads and writes involving the .mnc file are
  22550. performed using the sizeof operator to determine how many bytes to transfer.
  22551. Even writing a simple integer value, such as the menu count (line 273), should
  22552. employ sizeof rather than a constant byte count value such as 2 or 4.
  22553. After each item is written to disk, its memory block is freed (line 299). If
  22554. there are many menu definitions in a single menu specification file, then many
  22555. blocks of item memory will end up having been allocated and freed
  22556. repetitively. Since, however, cmenu is only run occasionally, the extra
  22557. overhead this repetition entails isn't enough to have a noticeable effect on
  22558. performance.
  22559. In rmenu, realtime efficiency is far more critical because many processes may
  22560. be active simultaneously. (I often see between ten and fifteen rmenu processes
  22561. running concurrently at the office.) Later we'll see how rmenu employs a
  22562. slightly more complex allocation strategy to avoid the memory thrashing
  22563. problem.
  22564.  
  22565.  
  22566. Error Reporting
  22567.  
  22568.  
  22569. The next set of functions in cmenu1.c are warning( ), error( ), and fatalerr(
  22570. ) (lines 307-394). These represent variations on a single theme: display a
  22571. diagnostic message on the standard error device (usually the user's terminal).
  22572. All three functions take a variable number of arguments, allowing format
  22573. conversions like printf( ) to be supported. Since pre-ANSI and post-ANSI C
  22574. differ in their provisions for functions that accept a variable number of
  22575. arguments, I found it necessary to include two different versions of the
  22576. starting sequence in each of the three error reporting functions.
  22577. ANSI C function headers allow the specification of ellipses (...) to indicate
  22578. a variable number of arguments in a function header or prototype. The macros
  22579. va_list and va_start, used in the processing of the variable length argument
  22580. list, are taken from the <stdarg.h> standard header file (see line 16).
  22581. Pre-ANSI C, on the other hand, cannot handle ellipses in function definitions.
  22582. Before the existence of ellipses and <stdarg.h>, functions taking a variable
  22583. number of arguments were processed with the macros defined in <stdarg.h>'s
  22584. predecessor, <varargs.h>. <varargs.h> contains alternate versions of the
  22585. macros va_list and va_start, plus an additional macro named va_alist that is
  22586. used in function headers as the placeholder for optional arguments.
  22587. The usages of <varargs.h> and <stdarg.h> differ only up to (and including) the
  22588. appearance of the va_start macro. After that, the methods are equivalent, so
  22589. the conditional portions of the three error reporting functions end at that
  22590. point.
  22591. Each diagnostic function calls fprintf( ) to send the name and current line
  22592. number of the source file involved to the standard error stream. Then they
  22593. call vfprintf( ) to send the specific error information suppled in the call to
  22594. the standard error stream. The only difference between the three functions is
  22595. in the way they affect the global error flags. warning( ) sets no flags.
  22596. error( ) sets only err_flag. fatalerr( ) sets both err_flag and fatal. Setting
  22597. the fatal flag causes processing of the current source file to be terminated
  22598. immediately upon return to the main processing loop, while err_flag is sampled
  22599. only after the current source file has been completely processed (without
  22600. fatal errors) to determine if an object file should be written.
  22601.  
  22602.  
  22603. String Matching
  22604.  
  22605.  
  22606. The matchkey( ) function searches the keyword table to see if a keyword exists
  22607. matching the given string, and returns the token value for that keyword if a
  22608. match is found.
  22609. The strstr( ) function tests if one given string is a subset of a second given
  22610. string. strstr( ) is supplied in case your library does not already include
  22611. it. Compilation of strstr( ) is conditionally controlled by the NEEDSTR
  22612. symbolic constant defined in the makefile.
  22613.  
  22614.  
  22615.  
  22616. Parsing The Input Stream
  22617.  
  22618.  
  22619. The two major components of cmenu remain to be discussed: token parsing, and
  22620. token processing. Because tokens must be recognized before they can be
  22621. processed, I'll begin with a description of the token parsing process. I'll
  22622. then tackle token processing to tie everything else together.
  22623. Tokens are the basic syntactic objects manipulated by cmenu. Each token is
  22624. represented by an integer value, sometimes in conjunction with the value of an
  22625. auxiliary variable. If the token represents a keyword or special condition,
  22626. then the integer value alone is sufficient to qualify it. If the token
  22627. represents a string or a numeric value, then the token value T_STRING or
  22628. T_VALUE in conjunction with the text in global array tparam (for strings), or
  22629. the integer value of the global vparam (for values) is needed to fully qualify
  22630. the token. You'll find definitions for tparam and vparam in ccmenu.h (see CUJ,
  22631. January, 1992). The token values are defined in lines 41-99.
  22632. The token parsing code resides in cmenu3.c (Listing 3). There are three
  22633. functions in this source file, two of which are called from other parts of the
  22634. program: gettok( ) to get the next token, and ungettok( ) to "unget" a token.
  22635. The last function, getword( ), is called only by gettok( ).
  22636.  
  22637.  
  22638. getword( ), The Workhorse
  22639.  
  22640.  
  22641. getword( ) does all the grunt work in the token parsing process. It recognizes
  22642. and skips over comments and whitespace, keeps track of the current line
  22643. number, recognizes both quoted and unquoted text strings, and maps unquoted
  22644. text strings into lowercase. getword( ) returns a pointer to a statically
  22645. allocated text string containing the text of the next token from the input
  22646. file without making any attempt to distinguish a keyword string from straight
  22647. text. (That job is handled by gettok( ).) Starting in line 126 (of Listing 3),
  22648. getword( ) checks for all the special cases just mentioned. Newlines cause the
  22649. lineno global to be incremented, while other whitespace is ignored (including
  22650. commas and semicolons). The only non-alphabetic keyword, the colon, requires a
  22651. special case. This simplifies the code to recognize other tokens (lines
  22652. 146-147).
  22653. Comments are handled by getword( ) in lines 149-162: all characters after the
  22654. leading # are ignored until a newline is encountered (or EOF, should the
  22655. trailing newline be missing). The line count is then incremented.
  22656. Lines 164-200 process quoted strings. First, the quoted_text flag is set to
  22657. TRUE so gettok( ) will know a string has been found. Then the string is
  22658. collected into the tok array, with appropriate action being taken when certain
  22659. special characters are encountered. Because multiline strings are not
  22660. supported, a newline in a string is treated as an error, as is an EOF
  22661. condition. If a double quote is encountered, the string is terminated with a
  22662. zero byte and a pointer to the string is returned to gettok( ).
  22663. Finally, an unquoted string (probably a keyword or an identifier, but possibly
  22664. a short action string or a pathname) is processed in lines 202-211. Any of the
  22665. usual separators or a colon terminate such a string. While it is being
  22666. collected up into the tok array, the string is converted to lowercase. As soon
  22667. as an illegal character is found, it is "ungotten" and a pointer to tok is
  22668. returned.
  22669.  
  22670.  
  22671. Back To gettok( ) And ungettok( )
  22672.  
  22673.  
  22674. At the top of cmenu3.c (lines 14-91), several items of static data are defined
  22675. to support the unget feature for tokens.This is a necessary feature for a
  22676. simple language processor like cmenu, because in some cases the code must scan
  22677. past the end of a clause to determine that the clause has come to an end. The
  22678. unget feature allows any token processing function to give back the extra
  22679. token. Then, next time the gettok( ) function is called to get a token from
  22680. the input stream, the ungotten token will be returned again. Only a single
  22681. level of ungetting is needed to process the CMENU syntax.
  22682. When gettok( ) is called it first checks for an active ungotten token. If
  22683. found, all the saved static token detail variables are copied into th eir
  22684. global counterparts and gettok( ) returns the saved token value.
  22685. If there wasn't an active ungotten token, it is time to get a new one. The
  22686. global detail variables tparam and vparam are cleared, and getword( ) is
  22687. called to fetch the next syntactic object from the input stream. Lines 85-98
  22688. handle the easy kinds of tokens: EOF, quoted string, colon, or simple reserved
  22689. word. (In the case of quoted text, the text needs to be copied into tparam.)
  22690. Lines 100-104 process integer values by setting vparam if a leading digit is
  22691. detected and returning T_VALUE. If we reach line 105, the token is treated as
  22692. an unquoted string: the text is copied into tparam, and T_STRING is returned.
  22693. The ungettok( ) function (lines 29-49) first checks to make sure there isn't
  22694. already a token pushed back. (There shouldn't ever be if the program is
  22695. working correctly; this test was put here only to catch possible development
  22696. bugs.) Then all the token detail values are saved in the static variables.
  22697.  
  22698.  
  22699. Token Processing
  22700.  
  22701.  
  22702. The cmenu code discussed up to this point all plays a supporting role to the
  22703. actual token processing functions in cmenu2.c. The call to each token
  22704. processing function is placed indirectly through the function pointers stored
  22705. in the keywords table. The dispatching takes place in the main processing loop
  22706. in cmenu1.c.
  22707. As each token processing function receives control, it can count on the
  22708. availability of certain information through global variables. We've already
  22709. seen how most of these global variables are managed. There's one more, named
  22710. token, that contains the most recently scanned token value (responsible for
  22711. arrival at a particular function in cmenu2). This variable is assigned in the
  22712. main processing loop, immediately before the dispatch is performed. We need to
  22713. know this value, because some token processing functions can handle more than
  22714. one token, and they examine the value of token to determine which token they
  22715. have been called to process.
  22716.  
  22717.  
  22718. The Menu Clause
  22719.  
  22720.  
  22721. To start at the beginning, the do_menu( ) function (Listing 2, line 20)
  22722. handles the start of a menu definition. The first thing that happens is a
  22723. check to see if the previous menu in the program was properly terminated with
  22724. endmenu. If it wasn't, then do_endmenu ( ) is called and a warning is issued.
  22725. If it was just a missing endmenu statement, the compilation will still
  22726. (begrudgingly) succeed.
  22727. do_menu ( ) then checks for the existence of a menu label by calling gettok( )
  22728. and seeing which token turns up next. If it is not a string, then no label was
  22729. given. This is only tolerated at the start of the file, because the omission
  22730. of a label in subsequent menus would prevent that menu from ever being
  22731. accessible (via the lmenu action). If the first menu is missing a label, a
  22732. dummy label is stuffed into tparam.
  22733. If the label is too long it is truncated (lines 38-43), and then a check is
  22734. made to see if the name appeared previously. If not, then we have the simple
  22735. case of a new definition, and lines 47-49 create an entry for it in the MInfo
  22736. array.
  22737. If the name has been used before (i.e., there's already an entry in MInfo for
  22738. it), then there are two possible explanations. Either it was used in a forward
  22739. lmenu reference, which is legitimate, or the label has already been used in
  22740. the definition of a previous menu, constituting an error (line 55). We can
  22741. tell which case it is by examining the Processed flag associated with that
  22742. MInfo entry.
  22743. When we get to line 57, MIp points to the MInfo entry for the new menu. We now
  22744. need to modify a group of elements of the Menu structure that is itself an
  22745. element of the MInfo entry. So, we assign the address of the Menu structure to
  22746. the pointer Mp, and then initialize everything in the Menu structure through
  22747. that pointer. Mp remains a valid pointer to that Menu structure after return
  22748. from do_menu ( ); several other functions will take advantage of it.
  22749. After all the attendant mode and structure initialization, we check for a
  22750. trailing colon in lines 68-69 and, if found, ignore it.
  22751.  
  22752.  
  22753. Menu Options
  22754.  
  22755.  
  22756. The next six functions (lines 75-257) deal with all the possible menu option
  22757. statements that, if present, must appear before the initial item clause.
  22758. do_title( ) and do_path( ) are very similar, each verifying the existence of
  22759. its required text string parameter, making sure the option hasn't appeared
  22760. before, and stuffing the string into the appropriate element of the current
  22761. Menu structure. (Mp comes in handy here.)
  22762. do_path( ) performs all the steps above, plus one more: it deletes any
  22763. trailing path delimiter character (slash or backslash) found at the end of the
  22764. path text. This prevents two consecutive path delimiter characters from
  22765. appearing in a path string when incremental paths are glued together.
  22766. The do_align( ) function was originally intended to support alternative ways
  22767. of aligning the item text on the screen. I later changed my mind about the
  22768. usefulness that option and never taught rmenu how to recognize it. The cmenu
  22769. code for processing the option remains, if someone thinks of a reason for
  22770. rmenu to use it.
  22771. do_spacing( ) and do_columns( ) are similar to do_title( ) and do_path( ), but
  22772. they take an integer instead of a text string for their parameter. I don't
  22773. think they require any further annotation.
  22774. The do_escape( ) function handles both the escape and noescape options,
  22775. checking for the usual error conditions and then setting the escape flag in
  22776. the Menu structure to either YES or NO. If this option does not appear, the
  22777. default value for the escape flag ends up being DEFAULT, which has a very
  22778. different meaning from both YES and NO.
  22779. The do_endmenu( ) function pulls clean-up duty after a menu definition. If no
  22780. items were found, that merits an error message. The forward reference table is
  22781. then checked to make sure all references have been resolved. If an unresolved
  22782. reference is found, then a little shuffle-play is performed involving the
  22783. global line number variable, lineno. This forces the error message function to
  22784. report the line number where the unresolved reference was made, instead of the
  22785. current line number.
  22786. Finally, global modes are reset in lines 284-287 with values indicating a "not
  22787. in menu" status. The Processed flag is set to show that the menu has now been
  22788. defined, and the number of items found is stashed away in the Menu structure
  22789. (squeezing some final mileage out of old venerable Mp).
  22790.  
  22791.  
  22792. The Item Clause
  22793.  
  22794.  
  22795.  
  22796. There is a lot of parallelism between the set of functions that process item
  22797. related statements and the set we just saw for handling menu related ones. I
  22798. suppose one could even describe the relationship between menus and items as
  22799. fractal, because the structure somewhat repeats itself. There is an item
  22800. clause with an optional identifier, followed by a set of item options, some of
  22801. which are required and some are optional. In the case of item definitions,
  22802. however, there is no requirement that the optional clauses precede the
  22803. required ones (the way menu options must precede all menu items).
  22804. There is no explicit keyword to mark the end of an item definition. The first
  22805. thing, therefore, the do_item( ) function (lines 291-357) must do is check
  22806. that the last item, if any, defined in the current menu included its required
  22807. clauses. The call to itemcheck( ) in line 303 does this.
  22808. Next, we test if a label is attached to the item definition. If not, a dummy
  22809. name is constructed; if so, the identifier name used is checked for legality
  22810. in lines 311-326.
  22811. Lines 328-329 make sure that the label name has not been used before in the
  22812. current menu. Even if a forward reference has been made to this label, it will
  22813. not show up in a call to find_item( ) because the forward reference
  22814. information has all been segregated into the fwd_refs table (to be examined
  22815. shortly).
  22816. Now we can create an entry for the new item, and place a pointer to it in the
  22817. Items array. The create_item( ) function (Listing 1, lines 160-192) allocates
  22818. memory for the new item info structure, initializes its members, and returns a
  22819. pointer to the structure. If there is a problem creating the array,
  22820. create_item( ) returns NULL and the process is aborted.
  22821. Having survived to line 335, the global mode in_item is set and a handy ITEM
  22822. pointer, Ip, is set to point to the ITEM structure element of the IINFO
  22823. structure just created. Ip may then be used throughout the remainder of this
  22824. item's processing.
  22825. Lines 338-340 resolve any possible forward references to the new item. The
  22826. fwd_refs table is scanned for an entry whose name matches the new item's. If
  22827. one is found, the lmenunum element of the item where the reference was made is
  22828. set (indirectly, through the refp pointer) to reflect the new item's index
  22829. value. This overwrites the previous item's lmenunum value of UNDEF_FWD,
  22830. thereby resolving the forward reference.
  22831. The last part of the do_item() function checks to see if the item text is
  22832. present as part of the item clause. To prevent ambiguity, I've required a
  22833. colon to precede any item text included directly in an item clause. Without
  22834. this restriction, the parser couldn't know if text found after the item
  22835. keyword was meant to be a label or an item text string. At this point in
  22836. do_item() (line 344), anything other than a colon terminates the item clause
  22837. processing.
  22838. Even if a colon is spotted, there may not be any item text given. Lines
  22839. 350-351 check for the item text and register it via do_text2( ) if present.
  22840.  
  22841.  
  22842. Item Options
  22843.  
  22844.  
  22845. The next six functions handle the various item options and the required action
  22846. clause. By now the pattern of these token processing functions should be
  22847. familiar. I'll skip the routine details and point out the highlights.
  22848. The do_opts( ) function handles a whole slew of trivial binary options by
  22849. setting Item flags to values triggered directly by the option tokens. There
  22850. are three functional categories processed by do_opts( ): prompting,
  22851. pre-clearing and post-clearing. Only one option from each of those three
  22852. categories is permitted to appear within any one item definition. The flag
  22853. associated with each category is initialized to the value DEFAULT. It keeps
  22854. that value until the appropriate statement forces the value to either YES or
  22855. NO.
  22856. The do_nextitem( ) function processes the nextitem clause, so the user may
  22857. alter the menu system's flow of control. One of four variations must be used.
  22858. The last is to supply a label for the next item to be highlighted. If the next
  22859. token after nextitem is a string, then find_item( ) is called to see if the
  22860. item has been defined. If it has, its index is inserted into the nextitem
  22861. element of the Item structure and no forward referencing is involved. If
  22862. find_item( ) couldn't find it, lines 432-437 install the item reference into
  22863. the forward reference table, for resolution when the item definition with the
  22864. given label finally appears. If you missed it, see "The Item Clause" section
  22865. above for related details.
  22866. If the screen text for the menu item was not specified in the item clause, it
  22867. must appear in a subsequent text clause. The do_text( ) and do_text2( )
  22868. functions process this option. After do_text( ) has checked the basics,
  22869. do_text2( ) tests whether the string is too long to store in the available
  22870. space. If it's too long, a message is printed and no attempt is made to copy
  22871. the string into the ITEM structure.
  22872. After copying a reasonable length string in through the Ip pointer, the flag
  22873. named widest (in the MENU structure) is updated to reflect the length of the
  22874. longest item text seen so far. This value will be used by rmenu when it comes
  22875. time to decide how all the menu items will be arranged on the screen.
  22876.  
  22877.  
  22878. Springing Into Action
  22879.  
  22880.  
  22881. There must be exactly one action associated with each menu item, from a choice
  22882. of four possibilities. The do_action( ) function is set to process all four
  22883. actions, each using a different token sequence.
  22884. The exit action may be specified either with or without the action keyword
  22885. preceding it. Both forms are valid.
  22886. The basic action clause is specified by the action keyword and a text string.
  22887. The string represents an operating system command (or sequence of commands
  22888. separated by semicolons). All do_action( ) does is copy the string (through
  22889. Ip) into the action element of the current ITEM structure (line 527). The
  22890. emenu action, which calls an external menu, is processed in the same way,
  22891. except that the acttyp element of the ITEM structure is set to identify the
  22892. action string as an external menu name rather than a command to be passed to
  22893. the system's command interpreter.
  22894. The fourth type of action is the lmenu clause, specifying a local menu to run.
  22895. If no menu by the given name is found in the file, a menu structure is created
  22896. and added to the MInfo array, and the index number of the new menu entry is
  22897. plugged into lmenunum element of the item being processed.
  22898. There is nothing new to say about the do_help( ) function, so I won't.
  22899. The final function in the module, do_err( ), gets called whenever a keyword is
  22900. encountered unexpectedly (for example when the first keyword is seen without a
  22901. leading nextitem). In the keywords table array, any keyword that does not
  22902. represent the beginning of a legitimate option sequence is assigned do_err( )
  22903. as its processing function. By calling the fatalerr( ) function to print its
  22904. error message, do_err( ) forces compilation to be terminated immediately after
  22905. the error message is printed, allowing the user to fix the syntax without
  22906. having to deal with additional spurious error messages.
  22907.  
  22908.  
  22909. Intermission: dmenu
  22910.  
  22911.  
  22912. This concludes my illustration of the cmenu program. Before delving into rmenu
  22913. in the next installment, I'd like to introduce a little diagnostic utility
  22914. named dmenu. I wrote this short program after eliminating the simple syntax
  22915. errors from my initial stab at the cmenu code, but before rmenu was written.
  22916. At that point, I needed a quick, easy way to examine cmenu's output files for
  22917. debugging purposes. dmenu reads an .mnc file and disassembles it into human
  22918. readable form, providing a complete picture of a compilation job performed by
  22919. cmenu. With the help of dmenu, I was able to debug cmenu quickly, without even
  22920. needing to have menu available. The code for dmenu.c is shown in Listing 4.
  22921.  
  22922. Listing 1
  22923. 1: /*************************************************************
  22924. 2: * Program: CMENU Menu Compiler
  22925. 3: * Module: cmenu1.c
  22926. 4: * Menu Compiler:
  22927. 5: * Main and Utility Functions
  22928. 6: * Written by: Leor Zolman, 7/91
  22929. 7: *************************************************************/
  22930. 8:
  22931. g: #define MASTER
  22932. 10: #include "cmenu.h"
  22933. 11: #include "ccmenu.h"
  22934. 12:
  22935. 13: #include <string.h>
  22936. 14:
  22937. 15: #if__STDC______LINEEND____
  22938. 16: # include <stdarg.h>
  22939. 17: #else
  22940. 18: include <varargs.h>
  22941. 19: #endif
  22942.  
  22943. 20:
  22944. 21: int main(argc,argv)
  22945. 22: int argc;
  22946. 23: char **argv;
  22947. 24: {
  22948. 25: register i;
  22949. 26:
  22950. 27: printf('CMENU Menu Compiler v%s\n", VERSION);
  22951. 28: if (argc < 2)
  22952. 29: {
  22953. 30: puts("usage: cmenu <menu-source-file(s)>\n");
  22954. 31: return 0;
  22955. 32: }
  22956. 33:
  22957. 34: for (i = 1; i <argc; i++)
  22958. 35: if (dofile(argv[i]) == ERROR) /* process source files */
  22959. 36: return 1;
  22960. 37: return 0;
  22961. 38: }
  22962. 39:
  22963. 40: /************************************************************
  22964. 41: * dofile():
  22965. 42: * Process a single .mnu source file
  22966. 43: ************************************************************/
  22967. 44:
  22968. 45: int dofile(name)
  22969. 46: char *name;
  22970. 47: {
  22971. 48: register i;
  22972. 49: char *cp;
  22973. 50:
  22974. 51: if ((cp = strstr(name, ".mnu")) 
  22975. 52: (cp = strstr(name, ".MNU")))
  22976. 53: *cp = '\0';
  22977. 54:
  22978. 55: strcpy(src_name, name);
  22979. 56: strcat(src_name, ".mnu");
  22980. 57: strcpy(obj_name, name);
  22981. 58:
  22982. 59: if ((fp = fopen(src_name, "r")) == NULL)
  22983. 60: return fprintf(stderr, "Can't open %s\n", src_name);
  22984. 61:
  22985. 62: n_menus = 0;
  22986. 63 lineno = 1;
  22987. 64: in_menu = FALSE;
  22988. 65: fatal = FALSE;
  22989. 66:
  22990. 67: /* Main processing loop. Read a token and process it,
  22991. 68: * until end of file is reached:
  22992. 69: */
  22993. 70:
  22994. 71: while ((token = gettok(fp)) != T_EOF)
  22995. 72: {
  22996. 73: if (!in_menu && token != T_MENU)
  22997. 74: {
  22998. 75: error("Each menu must begin with the Menu keyword");
  22999. 76: break;
  23000. 77: }
  23001. 78: if ((*keywords[token].t_func)() == ERROR)
  23002.  
  23003. 79: if (fatal) /* If fatal error, exit loop */
  23004. 80: break;
  23005. 81: }
  23006. 82:
  23007. 83: fclose(fp);
  23008. 84:
  23009. 85: if (!n_menus)
  23010. 86: return error("No menus defined");
  23011. 87:
  23012. 88: if (in_menu)
  23013. 89: {
  23014. 90: if (n_items)
  23015. 91: itemcheck();
  23016. 92: error("Menu definition missing \"Endmenu\" statement");
  23017. 93: }
  23018. 94:
  23019. 95: for (i = 0; i < n_menus; i++) /* check for undefined */
  23020. 96: if (!MInfo[i].Processed) /* "lmenu" references */
  23021. 97: {
  23022. 98: printf("Local Menu \"%s\" is undefined.\n",
  23023. 99: MInfo[i] .Name);
  23024. 100: err_flag = TRUE;
  23025. 101: }
  23026. 102:
  23027. 103: if (err_flag)
  23028. 104: return ERROR;
  23029. 105:
  23030. 106: if (write_file() == ERROR)
  23031. 107: return ERROR;
  23032. 108: return OK;
  23033. 109: }
  23034. 110:
  23035. 111:
  23036. 112: /***********************************************************
  23037. 113: * create_menu():
  23038. 114: * Construct a new menu information structure and
  23039. 115: * return it (by value).
  23040. 116: * Set fatal to TRUE if can't create.
  23041. 117: ***********************************************************/
  23042. 118:
  23043. 119: MINFO create_menu(name)
  23044. 120: char *name;
  23045. 121: {
  23046. 122: MINFO mi;
  23047. 123:
  23048. 124: if (n_menus == MAX_MENUS)
  23049. 125: fatalerr("Maximum # of menus (%d) exceeded", MAX_MENUS);
  23050. 126: else
  23051. 127: {
  23052. 128: strcpy(mi_Name, name);
  23053. 129: mi.Processed = FALSE;
  23054. 130: }
  23055. 131: return mi;
  23056. 132: }
  23057. 133:
  23058. 134:
  23059. 135: /***********************************************************
  23060. 136: * find_menu():
  23061. 137: * Search the Menu Info table for a named local menu.
  23062.  
  23063. 138: * If found:
  23064. 139: * Return a pointer to the entry if found, and set
  23065. 140: * global variable menu_num to the menu's index
  23066. 141: * else:
  23067. 142: * return NULL
  23068. 143: ***********************************************************/
  23069. 144:
  23070. 145: MINFO *find_menu(name)
  23071. 146: char *name;
  23072. 147: {
  23073. 148: int i;
  23074. 149:
  23075. 150: for (i = 0; i < n_menus; i++)
  23076. 151: if (!strcmp(MInfo[i].Name, name))
  23077. 152: {
  23078. 153: menu_num = i;
  23079. 154: return &MInfo[i];
  23080. 155: }
  23081. 156: return NULL;
  23082. 157: }
  23083. 158:
  23084. 159:
  23085. 160: /***********************************************************
  23086. 161: * create_item(): Allocate space for Item Info structure,
  23087. 162: * Initialize it and return a pointer to the structure
  23088. 163: * Return NULL if there was a creation error.
  23089. 164: ***********************************************************/
  23090. 165:
  23091. 166: IINFO *create_item(name)
  23092. 167: char *name;
  23093. 168: {
  23094. 169: IINFO *IIp;
  23095. 170: ITEM *Ip;
  23096. 171:
  23097. 172: if (n_items == MAX_ITEMS)
  23098. 173: {
  23099. 174: fatalerr("Max. # of items (%d) exceeded", MAX_ITEMS);
  23100. 175: return NULL;
  23101. 176: }
  23102. 177:
  23103. 178: if ((IIp =(IINFO*) malloc(sizeof(IINFO))) == NULL)
  23104. 179: {
  23105. 180: fatalerr("Out of memory");
  23106. 181: NULL;
  23107. 182: }
  23108. 183:
  23109. 184: strcpy(IIp->Name, name);
  23110. 185: Ip = &IIp->Item;
  23111. 186: Ip->acttyp = ACT_NONE;
  23112. 187: Ip->pre_clear = Ip->post_clear = Ip->prompt = DEFAULT;
  23113. 188: Ip->nextcode = DEFAULT;
  23114. 189: Ip->nextitem = Ip->lmenunum = 0;
  23115. 190: *Ip->text = *Ip->path = *Ip->action = *Ip->help = '\0';
  23116. 191: return IIp;
  23117. 192: }
  23118. 193:
  23119. 194:
  23120. 195: /***********************************************************
  23121. 196: * find_item():
  23122.  
  23123. 197: * Search the Item Info table for a named item in the
  23124. 198: * currently active menu definition.
  23125. 199: * If item name found:
  23126. 200: * Set item_num to the index value of the Item,
  23127. 201: * Return a pointer to the entry
  23128. 202: * else:
  23129. 203: * return NULL
  23130. 204: ***********************************************************/
  23131. 205:
  23132. 206: IINFO *find_item(name)
  23133. 207: char *name;
  23134. 208: {
  23135. 209: int i;
  23136. 210:
  23137. 211: for (i = 0; i < n_items; i++)
  23138. 212: if (!strcmp(MIp->Items[i]->Name, name))
  23139. 213: {
  23140. 214: item_num = i;
  23141. 215: return MIp->Items[i];
  23142. 216: }
  23143. 217: return NULL;
  23144. 218: }
  23145. 219:
  23146. 220:
  23147. 221: /***********************************************************
  23148. 222: * itemcheck():
  23149. 223: * Check the currently active item to make sure
  23150. 224: * both a Text and an Action clause have been
  23151. 225: * explicitly given.
  23152. 226: ***********************************************************/
  23153. 227:
  23154. 228: Void itemcheck()
  23155. 229: {
  23156. 230: if (!*Ip->text)
  23157. 231: error("No TEXT clause found for current item");
  23158. 232: if (Ip->acttyp == ACT_NONE)
  23159. 233: error("No ACTION clause found for current item");
  23160. 234: }
  23161. 235:
  23162. 236:
  23163. 237: /***********************************************************
  23164. 238: * write_file():
  23165. 239: * Write menu object file to disk, ready for
  23166. 240: * execution via menu.
  23167. 241: * Menu object file format:
  23168. 242: * --------------------------------
  23169. 243: * <count> (integer count of # of menus in file)
  23170. 244: * MENU 1 (MENU structure for 1st Menu)
  23171. 245: * ITEM 1
  23172. 246: * ITEM 2
  23173. 247: * ...
  23174. 248: * ITEM n_items
  23175. 249: * MENU 2 (MENU structure for 2nd Menu)
  23176. 250: * ...
  23177. 251: * .
  23178. 252: * .
  23179. 253: * .
  23180. 254: * MENU <count> (MENU structure for final Menu)
  23181. 255: * ...
  23182.  
  23183. 256: * --------------------------------
  23184. 257: *
  23185. 258: ***********************************************************/
  23186. 259:
  23187. 260: int write_file()
  23188. 261: {
  23189. 262: int i,j;
  23190. 263:
  23191. 264: strcat(obj_name, ".mnc");
  23192. 265:
  23193. 266: if ((fp = fopen(obj_name, "wb")) == NULL)
  23194. 267: {
  23195. 268: fprintf(stderr,
  23196. 269: "Cannot open %s for writing.\n", obj_name);
  23197. 270: return ERROR;
  23198. 271: }
  23199. 272:
  23200. 273: if (fwrite((Void *)&n_menus, sizeof n_menus, 1, fp) != 1)
  23201. 274: {
  23202. 275: fprintf(stderr,
  23203. 276: "Error writing menu count to %s\n", obj_name);
  23204. 277: return ERROR;
  23205. 278: }
  23206. 279:
  23207. 280: for (i = 0; i < n_menus; i++)
  23208. 281: {
  23209. 282: Mp = &MInfo[i].Menu;
  23210. 283: if (fwrite((Void *) Mp, sizeof (MENU), 1, fp) != 1)
  23211. 284: {
  23212. 285: fprintf(stderr,
  23213. 286: "Error writing to %s\n", obj_name);
  23214. 287: return ERROR;
  23215. 288: }
  23216. 289:
  23217. 290: for (j = 0; j < Mp->nitems; j++)
  23218. 291: {
  23219. 292: if (fwrite((Void *) &MInfo[i].Items[j]->Item,
  23220. 293: sizeof (ITEM), 1, fp) != 1)
  23221. 294: {
  23222. 295: fprintf(stderr,
  23223. 296: "Error writing to %s\n", obj_name);
  23224. 297: return ERROR;
  23225. 298: }
  23226. 299: free(MInfo[i].Items[j]);
  23227. 300: }
  23228. 301: }
  23229. 302: printf("Menu object file %s written.\n", obj_name);
  23230. 303: return OK;
  23231. 304: }
  23232. 305:
  23233. 306:
  23234. 307: /***********************************************************
  23235. 308: * warning():
  23236. 309: * Display a warning message, preceded by source
  23237. 310: * file name and line number, supporting format
  23238. 311: * conversions.
  23239. 312: ***********************************************************/
  23240. 313:
  23241. 314: #if_STDC_ /* ANSI variable-#-of-args method: */
  23242.  
  23243. 315: int warning(char *fmt, ...)
  23244. 316: {
  23245. 317: va_list arglist;
  23246. 318: va_start(arglist, fmt);
  23247. 319:
  23248. 320: #else /* old "varargs" method: */
  23249. 321: int warning(fmt, va_alist)
  23250. 322: char *fmt;
  23251. 323: va_dcl
  23252. 324: {
  23253. 325: va_list arglist;
  23254. 326: va_start(arglist);
  23255. 327: #endif
  23256. 328: /* the rest is the same, ANSI or varargs: */
  23257. 329:
  23258. 330: fprintf(stderr, "%s (%d): ", src_name, lineno);
  23259. 331: vfprintf(stderr, fmt, arglist);
  23260. 332: va_end(arglist);
  23261. 333: fprintf(stderr, "\n");
  23262. 334: return OK;
  23263. 335: }
  23264. 336:
  23265. 337:
  23266. 338: /***********************************************************
  23267. 339: * error():
  23268. 340: * Display an error message, preceded by source
  23269. 341: * file name and line number, supporting format
  23270. 342: * conversions.
  23271. 343: ***********************************************************/
  23272. 344:
  23273. 345: #if__STDC__ /* ANSI variable-#-of-args method: */
  23274. 346: int error(char *fmt, ...)
  23275. 347: {
  23276. 348: va_list arglist;
  23277. 349: va_start(arglist, fmt);
  23278. 350:
  23279. 351: #else /* old "varargs" method: */
  23280. 352: int error(fmt, va_alist)
  23281. 353: char *fmt;
  23282. 354: va_dcl
  23283. 355: {
  23284. 356: va_list arglist;
  23285. 357: va_start(arglist);
  23286. 358: #endif
  23287. 359:
  23288. 360: /* the rest is the same, ANSI or varargs: */
  23289. 361:
  23290. 362: fprintf(stderr, "%s (%d): ", src_name, lineno);
  23291. 363: vfprintf(stderr, fmt, arglist);
  23292. 364: va_end(arglist);
  23293. 365: fprintf(stderr, "\n");
  23294. 366: err_flag = TRUE;
  23295. 367: return ERROR;
  23296. 368: }
  23297. 369:
  23298. 370:
  23299. 371: /***********************************************************
  23300. 372: * fatalerr():
  23301. 373: * Like error, except global flag "fatal" is set.
  23302.  
  23303. 374: ***********************************************************/
  23304. 375:
  23305. 376: #if __STDC__ /* start function the ANSI way... */
  23306. 377: int fatalerr(char *fmt, ... /)
  23307. 378: {
  23308. 379: va_list arglist;
  23309. 380: va_start(arglist, fmt);
  23310. 381: #else /* or the old "varargs" way... */
  23311. 382: int fatalerr(fmt, va_alist)
  23312. 383: char *fmt;
  23313. 384: va_dcl
  23314. 385: {
  23315. 386: va_list arglist;
  23316. 387: va_start(arglist);
  23317. 388: #endif
  23318. 389:
  23319. 390: error(fmt, arglist); /* the rest is same, ANSI or varargs */
  23320. 391: va_end(arglist);
  23321. 392: fatal = TRUE;
  23322. 393: return ERROR;
  23323. 394: }
  23324. 395:
  23325. 396:
  23326. 397: /***********************************************************
  23327. 398: * matchkey():
  23328. 399: * Test if given string is a reserved word. Return the token
  23329. 400: * value of the matching token if so; else return NULL.
  23330. 401: ***********************************************************/
  23331. 402:
  23332. 403: int matchkey(str)
  23333. 404: char *str;
  23334. 405: {
  23335. 406: char str2[MAX_CMD], *cp;
  23336. 407: int i;
  23337. 408:
  23338. 409: strcpy(str2, str);
  23339. 410: for (cp = str2; *cp; cp++)
  23340. 411: *cp = tolower(*cp);
  23341. 412:
  23342. 413: for (i = 0; i < (int) N_KEYWORDS; i++)
  23343. 414: if (!strcmp(keywords[i].keyword, str2))
  23344. 415: return i;
  23345. 416:
  23346. 417: return NULL;
  23347. 418: }
  23348. 419:
  23349. 420:
  23350. 421: #ifdef NEEDSTR
  23351. 422: /*
  23352. 423: * Search for first occurence of substring s2 in s1:
  23353. 424: * (provided for Xenix only; this function is in
  23354. 425: * most modern standard distribution libraries)
  23355. 426: */
  23356. 427:
  23357. 428: char *strstr(s1, s2)
  23358. 429: char *s1, *s2;
  23359. 430: {
  23360. 431: int i, j, nposs;
  23361. 432: int len1 = strlen(s1);
  23362.  
  23363. 433:
  23364. 434: int len2 = strlen(s2);
  23365. 435: char *pl;
  23366. 436:
  23367. 437:
  23368. 438: if (len1 < len2)
  23369. 439: return NULL;
  23370. 440:
  23371. 441: nposs = len1 - len2;
  23372. 442:
  23373. 443: for (i = 0; i <= nposs; i++)
  23374. 444: {
  23375. 445: for (j = 0, p1 = &s1[i]; j < len2; j++)
  23376. 446: if (*pl++ != s2[j])
  23377. 447: break;
  23378. 448: if (j == len2)
  23379. 449: return &s1[i];
  23380. 450: }
  23381. 451: return NULL;
  23382. 452: }
  23383. 453: #endif
  23384. /* End of File */
  23385.  
  23386.  
  23387. Listing 2
  23388. 1: /*************************************************************
  23389. 2: * Program: CMENU Menu Compiler
  23390. 3: * Module: cmenu2.c
  23391. 4: * Menu Compiler:
  23392. 5: * Menu/Item Token Processing Functions
  23393. 6: * Written by: Leor Zolman, 7/91
  23394. 7: *************************************************************/
  23395. 8:
  23396. 9: #include "cmenu.h"
  23397. 10: #include "ccmenu.h"
  23398. 11:
  23399. 12: #include <ctype.h>
  23400. 13:
  23401. 14:
  23402. 15: /************************************************************
  23403. 16: * do_menu():
  23404. 17: * Process the MENU keyword
  23405. 18: *************************************************************/
  23406. 19:
  23407. 20: int do_menu()
  23408. 21: {
  23409. 22: int tok;
  23410. 23:
  23411. 24: if (in_menu) /* Are we currently processing a menu? */
  23412. 25: { /* yes. */
  23413. 26: warning("Endmenu missing from previous menu");
  23414. 27: do_endmenu(); /* give them the benefit of the doubt */
  23415. 28: }
  23416. 29:
  23417. 30: if ((tok = gettok()) != T_STRING)
  23418. 31: {
  23419. 32: if (n_menus)
  23420. 33: error("Menu name missing; menu unreachable");
  23421. 34: sprintf(tparam, "menu%d", n_menus + 1); /* force a name */
  23422.  
  23423. 35: ungettok(tok);
  23424. 36: }
  23425. 37:
  23426. 38: if (strlen(tparam) > MAX_NAME)
  23427. 39: {
  23428. 40: error("The name '%s' is too long (%d chars max)",
  23429. 41: tparam, MAX_NAME);
  23430. 42: tparam[MAX_NAME] = '\0'; /* truncate name */
  23431. 43: }
  23432. 44:
  23433. 45: if ((MIp = find_menu(tparam)) == NULL) /* menu exist? */
  23434. 46: {
  23435. 47: MInfo[n_menus] = create_menu(tparam); /* no. */
  23436. 48: if (fatal)
  23437. 49: return ERROR; /* creation error */
  23438. 50: else
  23439. 51: MIp = &MInfo[n_menus++]; /* OK, bump count */
  23440. 52: }
  23441. 53: else
  23442. 54: if (MIp -> Processed) /* redefinition? */
  23443. 55: return fatalerr("Duplicate Menu definition"); /* yes. */
  23444. 56:
  23445. 57: Mp = &MIp -> Menu;
  23446. 58: *Mp->title = *Mp->path = '\0';
  23447. 59: Mp->nitems = Mp->widest = 0;
  23448. 60: Mp->spacing = Mp->columns = Mp->escape = DEFAULT;
  23449. 61: Mp->align = DEFAULT;
  23450. 62:
  23451. 63: in_item = FALSE; /* state variables describing the */
  23452. 64: in_menu = TRUE; /* current menu being processed */
  23453. 65: n_items = 0;
  23454. 66: n_refs = 0; /* no forward item references yet */
  23455. 67:
  23456. 68: if ((tok = gettok()) != T_COLON) /* optional colon */
  23457. 69: ungettok(tok);
  23458. 70:
  23459. 71: return OK;
  23460. 72: }
  23461. 73:
  23462. 74:
  23463. 75: /************************************************************
  23464. 76: * do_title():
  23465. 77: * Process the TITLE clause for a menu.
  23466. 78: ************************************************************/
  23467. 79:
  23468. 80: int do_title()
  23469. 81: {
  23470. 82: int tok;
  23471. 83:
  23472. 84: if ((tok = gettok()) != T_STRING)
  23473. 85: {
  23474. 86: error("Title text missing");
  23475. 87: ungettok(tok);
  23476. 88: }
  23477. 89:
  23478. 90: if (!in_item) /* Before all items? */
  23479. 91: { /* yes. */
  23480. 92: if (*Mp->title)
  23481. 93: return error("A Menu Title has already been specified");
  23482.  
  23483. 94: strcpy(Mp->title, tparam);
  23484. 95: }
  23485. 96: else
  23486. 97: return error("The Menu Title must precede all Menu Items.");
  23487. 98:
  23488. 99: return OK;
  23489. 100: }
  23490. 101:
  23491. 102:
  23492. 103: /***********************************************************
  23493. 104: * do_path():
  23494. 105: * Process the PATH option.
  23495. 106: * Note that the PATH option may apply to an entire
  23496. 107: * menu or just to a single menu item (determined
  23497. 108: * by context.)
  23498. 109: ***********************************************************/
  23499. 110:
  23500. 111: int do_path()
  23501. 112: {
  23502. 113: int tok;
  23503. 114: char *cp;
  23504. 115:
  23505. 116: if ((tok = gettok()) != T_STRING)
  23506. 117: {
  23507. 118: error("Path text missing");
  23508. 119: ungettok(tok);
  23509. 120: }
  23510. 121:
  23511. 122: if (tparam[strlen(tparam)-1]=='/' tparam[strlen(tparam)-1]='\\')
  23512. 123: tparam[strlen(tparam) - 1] = '\0'; /* delete traling slash */
  23513. 124:
  23514. 125: if (!in_item) /* Referring to the menu? */
  23515. 126: { /* yes. */
  23516. 127: if (*Mp->path)
  23517. 128: return error("A Menu Path has already been specified");
  23518. 129: strcpy(Mp->path, tparam);
  23519. 130: }
  23520. 131: else
  23521. 132: { /* Must be for the item. */
  23522. 133: if (*Ip->path)
  23523. 134: return error("An Item Path has already been specified");
  23524. 135: strcpy(Ip->path, tparam);
  23525. 136: }
  23526. 137: return OK;
  23527. 138: }
  23528. 139:
  23529. 140:
  23530. 141: /***********************************************************
  23531. 142: * do_align():
  23532. 143: * Process text alignment option.
  23533. 144: * Note: this option is a no-op. I decided there wasn't
  23534. 145: * any real need for any other than left-justified item
  23535. 146: * alignment. But, in case anyone thinks of a use for it,
  23536. 147: * I'm leaving in the ability to process the option.
  23537. 148: ***********************************************************/
  23538. 149:
  23539. 150: int do_align()
  23540. 151: {
  23541. 152: int tok;
  23542.  
  23543. 153:
  23544. 154: if (in_item)
  23545. 155: return error("The Align clause must precede all Menu Items.");
  23546. 156:
  23547. 157: if (Mp->align)
  23548. 158: return error("Align option already specified for this menu");
  23549. 159:
  23550. 160: switch (tok = gettok())
  23551. 161: {
  23552. 162: case T_LEFT:
  23553. 163: Mp->align = 'L';
  23554. 164: break;
  23555. 165:
  23556. 166: case T_RIGHT:
  23557. 167: Mp->align = 'R';
  23558. 168: break;
  23559. 169:
  23560. 170: case T_CENTER:
  23561. 171: Mp->align = 'C';
  23562. 172: break;
  23563. 173:
  23564. 174: default:
  23565. 175: ungettok(tok);
  23566. 176: return error("Align missing valid modifier");
  23567. 177: }
  23568. 178: return OK;
  23569. 179: }
  23570. 180:
  23571. 181:
  23572. 182: /***********************************************************
  23573. 183: * do_spacing():
  23574. 184: * Process the SPACING option (applies
  23575. 185: * to menus only.)
  23576. 186: ***********************************************************/
  23577. 187:
  23578. 188: int do_spacing()
  23579. 189: {
  23580. 190: int tok;
  23581. 191:
  23582. 192: if ((tok = gettok()) != T_VALUE)
  23583. 193: {
  23584. 194: error("Spacing value missing");
  23585. 195: ungettok(tok);
  23586. 196: }
  23587. 197:
  23588. 198: if (in_item)
  23589. 199: return error("Spacing option must precede all menu items");
  23590. 200:
  23591. 201: if (Mp->spacing)
  23592. 202: return error("Spacing option already specified");
  23593. 203:
  23594. 204: /* only single and double spacing supported */
  23595. 205: if (vparam != 1 && vparam != 2)
  23596. 206: return error("Spacing value must be either 1 or 2");
  23597. 207:
  23598. 208: Mp->spacing = vparam;
  23599. 209: return OK;
  23600. 210: }
  23601. 211:
  23602.  
  23603. 212:
  23604. 213: /***********************************************************
  23605. 214: * do_columns():
  23606. 215: * Process the COLUMNS option
  23607. 216: ***********************************************************/
  23608. 217:
  23609. 218: int do_columns()
  23610. 219: {
  23611. 220: int tok;
  23612. 221:
  23613. 222: if ((tok = gettok()) != T_VALUE)
  23614. 223: {
  23615. 224: error("Colunms value missing");
  23616. 225: ungettok(tok);
  23617. 226: }
  23618. 227:
  23619. 228: if (in_item)
  23620. 229: return error("Columns option must precede all menu items");
  23621. 230:
  23622. 231: if (Mp->columns)
  23623. 232: return error("Columns option already specified");
  23624. 233:
  23625. 234: if (vparam < 1 vparam > 6) /* 6 seems a reasonable max. */
  23626. 235: return error("Columns value must be between 1 and 6");
  23627. 236: Mp->columns = vparam;
  23628. 237: return OK;
  23629. 238: }
  23630. 239:
  23631. 240:
  23632. 241: /***********************************************************
  23633. 242: * do_escape():
  23634. 243: * Process "escape" and "noescape" menu options
  23635. 244: ***********************************************************/
  23636. 245:
  23637. 246: int do_escape()
  23638. 247: {
  23639. 248: if (in_item)
  23640. 249: return error("\"%s\" must appear before all menu items",
  23641. 250: keywords[token] .keyword);
  23642. 251:
  23643. 252: if (Mp->escape)
  23644. 253: return error("Escape option already specified");
  23645. 254: Mp->escape = (token == T_ESCAPE) ? YES : NO;
  23646. 255:
  23647. 256: return OK;
  23648. 257: }
  23649. 258:
  23650. 259:
  23651. 260: /***********************************************************
  23652. 261: * do_endmenu():
  23653. 262: * Process ENDMENU keyword
  23654. 263: ***********************************************************/
  23655. 264:
  23656. 265: int do_endmenu()
  23657. 266: {
  23658. 267: int i;
  23659. 268:
  23660. 269: if (!n_items)
  23661. 270: error("No menu items specified for this menu");
  23662.  
  23663. 271:
  23664. 272: for (i = 0; i < n_refs; i++) /* check for unresolved */
  23665. 273: { /* forward item references */
  23666. 274: if (*fwd_refs[i].refp == UNDEF_FWD)
  23667. 275: {
  23668. 276: int save_lineno = lineno;
  23669. 277: lineno = fwd_refs[i] .lineno;
  23670. 278: error("Unresolved reference to Item \"%s\"",
  23671. 279: fwd_refs[i].iname);
  23672. 280: lineno = save_lineno;
  23673. 281: }
  23674. 282: }
  23675. 283:
  23676. 284: in_menu = in_item = FALSE; /* done with current menu */
  23677. 285: MIp -> Processed = TRUE; /* it is now processed */
  23678. 286: Mp -> nitems = n_items;
  23679. 287: return OK;
  23680. 288: }
  23681. 289:
  23682. 290:
  23683. 291 /************************************************************
  23684. 292: * do_item():
  23685. 293: * Process the ITEM clause. Create a new ite
  23686. 294: * and fill in any existing forward references to it.
  23687. 295: ************************************************************/
  23688. 296:
  23689. 297: int do_item()
  23690. 298: {
  23691. 299: int tok, i;
  23692. 300: char *cp, c;
  23693. 301:
  23694. 302: if (n_items)
  23695. 303: itemcheck(); /* check for previous item's completion */
  23696. 304:
  23697. 305: if ((tok = gettok()) != T_STRING) /* label specified? */
  23698. 306: { /* If not, stuff unique */
  23699. 307: sprintf(tparam,"dummy!%d", n_items); /* dummy name in */
  23700. 308: ungettok(tok);
  23701. 309: }
  23702. 310: else
  23703. 311: {
  23704. 312: if (strlen(tparam) > MAX_NAME)
  23705. 313: {
  23706. 314: error("Item name \"%s\" too long. Max %d chars.",
  23707. 315: tparam, MAX_NAME);
  23708. 316: tparam[MAX_NAME] = '\0';
  23709. 317: }
  23710. 318: else for (cp = tparam; c = *cp; cp++)
  23711. 319: if (!(isalpha(c) isdigit(c) c == '_'))
  23712. 320: {
  23713. 321: error("Invalid char in identifier name: \"%s\"",
  23714. 322: tparam);
  23715. 323: *cp = '\0';
  23716. 324: break;
  23717. 325: }
  23718. 326: }
  23719. 327:
  23720. 328: if ((IIp = find_item(tparam)) != NULL) /" Item name found? */
  23721. 329: return error("Item name previously used.");
  23722.  
  23723. 330:
  23724. 331: if ((MIp->Items[n_items] =IIp = create_item(tparam))
  23725. 332: == NULL)
  23726. 333: return ERROR;
  23727. 334:
  23728. 335: in_item = TRUE;
  23729. 336: Ip = &IIp->Item;
  23730. 337:
  23731. 338: for (i = 0; i < n_refs; i++) /* check for fwd refs */
  23732. 339: if (!strcmp(fwd_refs[i].iname, tparam))
  23733. 340: *fwd_refs[i].refp = n_items; /* fill in with item # */
  23734. 341:
  23735. 342: n_items++; /* bump item count */
  23736. 343:
  23737. 344: if ((token = gettok()) != T_COLON) /* optional colon? */
  23738. 345: {
  23739. 346: ungettok(token); /* if not, all done */
  23740. 347: return OK;
  23741. 348: }
  23742. 349:
  23743. 350: if ((token = gettok()) == T_STRING) /* short-form text? */
  23744. 351: return do_text2(); /* if so, go process */
  23745. 352: else
  23746. 353: {
  23747. 354: ungettok(token); /* else all done */
  23748. 355: return OK;
  23749. 356: }
  23750. 357: }
  23751. 358:
  23752. 359:
  23753. 360: /***********************************************************
  23754. 361: * do_opts():
  23755. 362: * Process simple "binary" options for prompt,
  23756. 363: * pre- and post-clear specifications.
  23757. 364: * Note: upon entry, global "token" contains the
  23758. 365: * value of the token to be processed.
  23759. 366: ***********************************************************/
  23760. 367:
  23761. 368: int do_opts()
  23762. 369: {
  23763. 370: if (!in_item)
  23764. 371: return error("\"%s\" only valid within an item",
  23765. 372: keywords[token].keyword);
  23766. 373:
  23767. 374: switch(token)
  23768. 375: {
  23769. 376: case T_PROMPT: case T_PAUSE:
  23770. 377: case T_NOPROMPT: case T_NOPAUSE:
  23771. 378: if (Ip->prompt != DEFAULT)
  23772. 379: return error("Prompt option already specified");
  23773. 380: Ip->prompt= (token==T_PROMPT token==T_PAUSE)? YES :NO;
  23774. 381: break;
  23775. 382:
  23776. 383: case T_POSTCLEAR: /* these are actually no-ops, */
  23777. 384: case T_NOPOSTCLEAR: /* but again, I've left them in */
  23778. 385: if (Ip->post_clear != DEFAULT)
  23779. 386: return error("Postclear option already specified");
  23780. 387: Ip->post_clear = (token == T_POSTCLEAR) ? YES : NO;
  23781. 388: break;
  23782.  
  23783. 389:
  23784. 390: case T_PRECLEAR:
  23785. 391: case T_NOPRECLEAR:
  23786. 392: if (Ip->pre_clear != DEFAULT)
  23787. 393: return error("Preclear option already specified");
  23788. 394: Ip->pre_clear = (token == T_PRECLEAR) ? YES : NO;
  23789. 395: break;
  23790. 396: }
  23791. 397: return OK;
  23792. 398: }
  23793. 399:
  23794. 400:
  23795. 401: /***********************************************************
  23796. 402: * do_nextitem():
  23797. 403: * Process NEXTIEM option.
  23798. 404: ***********************************************************/
  23799. 405:
  23800. 406: int do_nextitem()
  23801. 407: {
  23802. 408: int tok;
  23803. 409:
  23804. 410: if (Ip->nextcode != DEFAULT)
  23805. 411: error("Nextitem option already specified");
  23806. 412:
  23807. 413: switch (tok = gettok())
  23808. 414: {
  23809. 415: case T_FIRST:
  23810. 416: Ip->nextcode = NXT_FIRST;
  23811. 417: break;
  23812. 418:
  23813. 419: case T_LAST:
  23814. 420: Ip->nextcode = NXT_LAST;
  23815. 421: break;
  23816. 422:
  23817. 423: case T_NEXT:
  23818. 424: Ip->nextcode = NXT_NEXT;
  23819. 425: break;
  23820. 426:
  23821. 427: case T_STRING:
  23822. 428: Ip->nextcode = NXT_DIRECT;
  23823. 429: if (find_item(tparam))
  23824. 430: Ip->nextitem = item_num;
  23825. 431: else
  23826. 432: { /* record forward item reference */
  23827. 433: strcpy(fwd_refs[n_refs] .iname, tparam);
  23828. 434: fwd_refs[n_refs].refp = &Ip->nextitem;
  23829. 435: fwd_refs[n_refs++].lineno = lineno;
  23830. 436: Ip->nextitem = UNDEF_FWD;
  23831. 437: }
  23832. 438: break;
  23833. 439:
  23834. 440: default:
  23835. 441: ungettok(tok);
  23836. 442: return error("Bad Nextitem specification");
  23837. 443: }
  23838. 444: return OK;
  23839. 445: }
  23840. 446:
  23841. 447:
  23842.  
  23843. 448: /***********************************************************
  23844. 449: * do_text():
  23845. 450: * Process Text parameter
  23846. 451: ***********************************************************/
  23847. 452:
  23848. 453: int do_text()
  23849. 454: {
  23850. 455: int tok;
  23851. 456:
  23852. 457: if (!in_item)
  23853. 458: return error("Text clause must be within an item");
  23854. 459: if (*Ip->text)
  23855. 460: return error("Text clause already specified for this item");
  23856. 461:
  23857. 462: if ((tok = gettok()) != T_STRING)
  23858. 463: {
  23859. 464: ungettok(tok);
  23860. 465: return error("Text clause specified without the text.");
  23861. 466: }
  23862. 467:
  23863. 468: return do_text2();
  23864. 469: }
  23865. 470:
  23866. 471:
  23867. 472: /***********************************************************
  23868. 473: * do_text():
  23869. 474: * Continued TEXT clause processing, shared between
  23870. 475: * do_text() and do_item().
  23871. 476: ***********************************************************/
  23872. 477:
  23873. 478: int do_text2()
  23874. 479: {
  23875. 480: if (strlen(tparam) > MAX_TXTWID)
  23876. 481: {
  23877. 482: *Ip->text = 'x'; /* to avoid "missing text" error */
  23878. 483: return error("Text is too long; maximum %d chars",
  23879. 484: MAX_TXTWID);
  23880. 485: }
  23881. 486: else
  23882. 487: strcpy(Ip->text, tparam);
  23883. 488:
  23884. 489: if (strlen(tparam) > Mp -> widest)
  23885. 490: Mp -> widest = strlen(tparam);
  23886. 491:
  23887. 492: return OK;
  23888. 493: }
  23889. 494:
  23890. 495:
  23891. 496: /***********************************************************
  23892. 497: * do_action():
  23893. 498: * Process standard action, Exit, Lmenu or Emenu clause
  23894. 499: ***********************************************************/
  23895. 500:
  23896. 501: int do_action()
  23897. 502: {
  23898. 503: int tok;
  23899. 504: int old_acttyp = Ip->acttyp;
  23900. 505:
  23901. 506: if (!in_item)
  23902.  
  23903. 507: return error("%s clause only valid within an item",
  23904. 508: keywords[token].keyword);
  23905. 509:
  23906. 510: if (token == T_EXIT (tok = gettok()) == T_EXIT)
  23907. 511: Ip->acttyp = ACT_EXIT;
  23908. 512: else
  23909. 513: if (tok !=T_STRING)
  23910. 514: {
  23911. 515: ungettok(tok);
  23912. 516: error("Incomplete %s specification",
  23913. 517: keywords[token] .keyword);
  23914. 518: }
  23915. 519: else
  23916. 520: if (strlen(tparam) > MAX_CHD)
  23917. 521: error("%s parameter too long (max %d chars)",
  23918. 522: keywords[token] .keyword, MAX_CMD);
  23919. 523: else
  23920. 524: switch(token)
  23921. 525: {
  23922. 526: case T_ACTION:
  23923. 527: strcpy(Ip->action, tparam);
  23924. 528: Ip->acttyp = ACT_CMND;
  23925. 529: break;
  23926. 530:
  23927. 531: case T_L_MENU:
  23928. 532: if (find_menu(tparam) != NULL) /* named menu defined? */
  23929. 533: Ip->lmenunum = menu_num; /* yes, */
  23930. 534: else
  23931. 535: { /* no. create entry */
  23932. 536: MInfo[n_menus] = create_menu(tparam);
  23933. 537: if (fatal)
  23934. 538: return ERROR; /* creation error */
  23935. 539: else
  23936. 540: Ip->lmenunum = n_menus++; /* Ok; assign. */
  23937. 541: }
  23938. 542:
  23939. 543: Ip->acttyp = ACT_LMENU;
  23940. 544: break;
  23941. 545:
  23942. 546: case T_EMENU:
  23943. 547: strcpy(Ip->action, tparam);
  23944. 548: Ip->acttyp = ACT_EMENU;
  23945. 549: break;
  23946. 550: }
  23947. 551:
  23948. 552: if (old_acttyp)
  23949. 553: return error("Only one Action clause allowed per item");
  23950. 554:
  23951. 555: return OK;
  23952. 556: }
  23953. 557:
  23954. 558:
  23955. 559: /***********************************************************
  23956. 560: * do_help():
  23957. 561: * Process help clause.
  23958. 562: ***********************************************************/
  23959. 563:
  23960. 564: int do_help()
  23961. 565: {
  23962.  
  23963. 566: int tok;
  23964. 567:
  23965. 568: if (!in_item)
  23966. 569: return error("Help clause only valid within an item");
  23967. 570:
  23968. 571: if ((tok = gettok()) != T_STRING)
  23969. 572: {
  23970. 573: ungettok(tok);
  23971. 574: return error("No Help text specified");
  23972. 575: }
  23973. 576:
  23974. 577: if (strlen(tparam) > MAX_HELP)
  23975. 578: return error("Help text too long (max %d chars)",
  23976. 579: MAX_HELP);
  23977. 580:
  23978. 581: if (*Ip->help)
  23979. 582: return error("Only one help line allowed per item");
  23980. 583:
  23981. 584: strcpy(Ip->help, tparam);
  23982. 585: return OK;
  23983. 586: }
  23984. 587:
  23985. 588:
  23986. 589: /***********************************************************
  23987. 590: * do_err():
  23988. 591: * Diagnose hopelessly bad syntax (i.e., encountering a
  23989. 592: * totally unexpected keyword)
  23990. 593: ***********************************************************/
  23991. 594:
  23992. 595: int do_err()
  23993. 596: {
  23994. 597: return fatalerr("Unrecoverable Syntax error.");
  23995. 598: }
  23996. 599:
  23997. /* End of File */
  23998.  
  23999.  
  24000. Listing 3
  24001. 1: /*************************************************************
  24002. 2: * Program: CMENU Menu Compiler
  24003. 3: * Module: cmenu3.c
  24004. 4: * Menu Compiler:
  24005. 5: * Token Processing Functions
  24006. 6: * Written by: Leor Zolman, 7/91
  24007. 7: *************************************************************/
  24008. 8:
  24009. 9: #include "cmenu.h"
  24010. 10: #include "cmenu.h"
  24011. 11:
  24012. 12: #include <ctype.h>
  24013. 13:
  24014. 14: static int unget_flag = FALSE;
  24015. 15: static int unget_token;
  24016. 16: static char unget_tparam[MAX_CMD];
  24017. 17: static int unget_vparam;
  24018. 18:
  24019. 19: static int quoted_text;
  24020. 20:
  24021. 21: #if_STDC_____LINEEND____
  24022.  
  24023. 22: char *getword(void);
  24024. 23: int matchkey(char *);
  24025. 24: #else
  24026. 25: char *getword();
  24027. 26: int matchkey();
  24028. 27: #endif
  24029. 28:
  24030. 29: /************************************************************
  24031. 30: * ungettok():
  24032. 31: * Push a token back into the input stream, to
  24033. 32: * be returned by the following call to gettok().
  24034. 33: * Only one level of push-back is supported; any attempt to
  24035. 34: * push back a token when there is already one pushed back
  24036. 35: * draws an "internal error" diagnostic.
  24037. 36: ************************************************************/
  24038. 37:
  24039. 38: Void ungettok(tok)
  24040. 39: int tok;
  24041. 40: {
  24042. 41: if (unget_flag) /* can't "unget" more than 1 item! */
  24043. 42: fatalerr("internal error: ungettok() overflow");
  24044. 43:
  24045. 44: unget_flag = TRUE;
  24046. 45: unget_token = tok;
  24047. 46: unget_vparam = vparam;
  24048. 47: strcpy(unget_tparam, tparam);
  24049. 48: return;
  24050. 49: }
  24051. 50:
  24052. 51:
  24053. 52: /************************************************************
  24054. 53: * gettok():
  24055. 54: * Read a token from the source input stream.
  24056. 55: * If the token is a reserved word, the appropriate token
  24057. 56: * value is returned.
  24058. 57: * If the token is a string, the global "tparam" is set
  24059. 58: * to the text of the string. White space within the
  24060. 59: * string is only recognized if double quote ("...")
  24061. 60: * characters are used to delimit the string.
  24062. 61: * T_STRING is returned.
  24063. 62: * If the token is a numeric value, the global "vparam"
  24064. 63: * is set to the integer value specified, and
  24065. 64 * T_VALUE is returned.
  24066. 65 * Returns T_EOF on end-of-file.
  24067. 66: ************************************************************/
  24068. 67:
  24069. 68: int gettok()
  24070. 69: {
  24071. 70: register c;
  24072. 71: char nexttok[MAX_CMD];
  24073. 72: char *wordp;
  24074. 73:
  24075. 74: if (unget_flag) /* was a token "pushed back"? */
  24076. 75: { /* yes. set the pushed-back values and */
  24077. 76: vparam = unget_vparam; /* attributes */
  24078. 77: strcpy(tparam, unget_tparam); /* clear the */
  24079. 78: unget_flag = FALSE; /* flag */
  24080. 79: return unget_token; /* return pushed token */
  24081. 80: }
  24082.  
  24083. 81:
  24084. 82: *tparam = '\0'; /* clear parameter */
  24085. 83: vparam = 0; /* value registers */
  24086. 84:
  24087. 85: if (!*(wordp = getword())) /* get a token. */
  24088. 86: return token = T_EOF; /* End of file */
  24089. 87:
  24090. 88: if (quoted_text) /* string enclosed */
  24091. 89: { /* in quotes? */
  24092. 90: strcpy(tparam, wordp);
  24093. 91: return T_STRING;
  24094. 92: }
  24095. 93:
  24096. 94: if (!strcmp(wordp, ":")) /* colon is special */
  24097. 95: return T_COLON; /* (non-alphabetic)*/
  24098. 96:
  24099. 97: if (c = matchkey(wordp)) /* reserved word? */
  24100. 98: return c; /* yes, just return */
  24101. 99:
  24102. 100: if (isdigit(*wordp)) /* handle numeric value */
  24103. 101: {
  24104. 102: vparam = atoi(wordp);
  24105. 103: return T_VALUE;
  24106. 104: }
  24107. 105: else
  24108. 106: {
  24109. 107: strcpy(tparam, wordp);
  24110. 108: return T_STRING;
  24111. 109: }
  24112. 110: }
  24113. 111:
  24114. 112:
  24115. 113: /***********************************************************
  24116. 114: * getword():
  24117. 115: * Read the next syntactic object from the input stream,
  24118. 116: * and return a pointer to it.
  24119. 117: * Return pointer to a null string on EOF.
  24120. 118: * If object is a quoted string, drop the quotes and
  24121. 119: * set the quoted_text flag (preserve whitespace).
  24122. 120: * Otherwise strip all whitespace, commas and comments,
  24123. 121: * return pointer to next word/number.
  24124. 122: * Track current line number by incrementing lineno
  24125. 123: * on each newline encountered.
  24126. 124: ***********************************************************/
  24127. 125:
  24128. 126: char *getword()
  24129. 127: {
  24130. 128: static char tok[MAX_CMD];
  24131. 129: char quote_char;
  24132. 130: register c,i;
  24133. 131:
  24134. 132: quoted_text = FALSE;
  24135. 133: *tok = '\0';
  24136. 134:
  24137. 135: while ((c = getc(fp)) != EOF)
  24138. 136: {
  24139. 137: if (c == '\n') /* bump line number if */
  24140. 138: lineno++; /* newline encountered */
  24141. 139:
  24142.  
  24143. 140: if (isspace(c)) /* skip all whitespace */
  24144. 141: continue;
  24145. 142:
  24146. 143: if (c == ',' c == ';') /*legal separators: ,; */
  24147. 144: continue;
  24148. 145:
  24149. 146: if (c == ':') /* special case: colon */
  24150. 147: return ":";
  24151. 148:
  24152. 149: if (c == '#') /* process comment */
  24153. 150: { /* wait for newline or EOF */
  24154. 151: while(c = getc(fp))
  24155. 152: {
  24156. 153: if (c == EOF)
  24157. 154: break;
  24158. 155: if (c == '\n')
  24159. 156: {
  24160. 157: lineno++;
  24161. 158: break;
  24162. 159: }
  24163. 160: }
  24164. 161: continue; /* then get next token */
  24165. 162: }
  24166. 163:
  24167. 164: if (c == '"' c == '\" ) /* quoted string? */
  24168. 165: {
  24169. 166: quoted_text = TRUE;
  24170. 167: quote_char = c;
  24171. 168: for (i = 0; ;i++)
  24172. 169: {
  24173. 170: switch (c = getc(fp))
  24174. 171: {
  24175. 172: case '\n':
  24176. 173: fatalerr("Unterminated string");
  24177. 174: return NULL;
  24178. 175:
  24179. 176: case EOF:
  24180. 177: fatalerr("Unterminated string (line %d)",
  24181. 178: lineno);
  24182. 179: return NULL;
  24183. 180:
  24184. 181: case ' " ':
  24185. 182: case '\":
  24186. 183: if (c == quote_char)
  24187. 184: {
  24188. 185: tok[i] = '\0';
  24189. 186: return tok;
  24190. 187: }
  24191. 188:
  24192. 189: default:
  24193. 190: if (i == MAX_CMD)
  24194. 191: {
  24195. 192: tok[i - 1] = '\0';
  24196. 193: fatalerr("String too long (max %d chars)",
  24197. 194: MAX_CMD);
  24198. 195: return NULL;
  24199. 196: }
  24200. 197: tok[i] = c;
  24201. 198: }
  24202.  
  24203. 199: }
  24204. 200: }
  24205. 201:
  24206. 202: tok[0] = c;
  24207. 203: for (i = 1; (c = getc(fp)) != EOF; i++)
  24208. 204: if (isspace(c) c == ';' c == ','
  24209. 205: c == ':')
  24210. 206: break;
  24211. 207: else
  24212. 208: tok[i] = tolower(c);
  24213. 209: tok[i] = '\0';
  24214. 210: ungetc(c, fp);
  24215. 211: break;
  24216. 212: }
  24217. 213: return tok;
  24218. 214: }
  24219. 215:
  24220. /* End of File */
  24221.  
  24222.  
  24223. Listing 4
  24224. 1: /*************************************************************
  24225. 2: * Program: DMENU Menu Object File Analyzer
  24226. 3: * Module: dmenu.c
  24227. 4: * dmenu.c: Reads a menu object file, for debuging menu
  24228. 5: * compiler
  24229. 6: *
  24230. 7: * Written by: Leor Zolman, 7/91
  24231. 8: *
  24232. 9: * Menu object file format:
  24233. 10: *-----------------------------------------------------------
  24234. 11: * <count> (integer count of # of menus in file)
  24235. 12: * MENU 1 (MENU structure for 1st Menu)
  24236. 13: * ITEM 1
  24237. 14: * ITEM 2
  24238. 15: * ...
  24239. 16: * ITEM n_items
  24240. 17: * MENU 2 (MENU structure for 2nd Menu)
  24241. 18: * ...
  24242. 19: * .
  24243. 20: * .
  24244. 21: * .
  24245. 22: * MENU <count> (MENU structure for final Menu)
  24246. 23: * ...
  24247. 24: *
  24248. 25: * ----------------------------------------------------------
  24249. 26: * Compile:
  24250. 27: * Xenix: cc dmenu.c -o dmenu
  24251. 28: * DOS: bcc dmenu.c (Borland C++)
  24252. 29: ************************************************************/
  24253. 30:
  24254. 31: #include <stdio.h>
  24255. 32: #include "cmenu.h"
  24256. 33:
  24257. 34: #define OK 0
  24258. 35:
  24259. 36: MENU Menu;
  24260. 37: ITEM Item, *ip = &Item;
  24261. 38:
  24262.  
  24263. 39: char obj_name[MAX_CMD];
  24264. 40: int n_menus;
  24265. 41:
  24266. 42: int main(argc,argv)
  24267. 43: int argc;
  24268. 44: char **argv;
  24269. 45: {
  24270. 46: register i, j;
  24271. 47: int count;
  24272. 48: FILE *fp;
  24273. 49:
  24274. 50: if (argc != 2)
  24275. 51: exit(puts("usage: dmenu <menu-object>\n"));
  24276. 52:
  24277. 53: strcpy(obj_name, argv[1]);
  24278. 54: strcat(obj_name, ".mnc");
  24279. 55:
  24280. 56: if ((fp = fopen(obj_name, "rb")) == NULL)
  24281. 57: {
  24282. 58: fprintf(stderr,
  24283. 59: "Cannot open %s for reading.\n", obj_name);
  24284. 60: return ERROR;
  24285. 61: }
  24286. 62:
  24287. 63: if (fread((Void *)&n_menus, sizeof n_menus, 1, fp) != 1)
  24288. 64: {
  24289. 65: fprintf(stderr,
  24290. 66: "Error reading menu count from %s\n", obj_name);
  24291. 67: return ERROR;
  24292. 68: }
  24293. 69: printf("Menu count = %d\n", n_menus);
  24294. 70:
  24295. 71: for (i = 0; i < n_menus; i++)
  24296. 72: {
  24297. 73: if (fread((Void *) &Menu, sizeof (MENU), 1, fp) != 1)
  24298. 74: {
  24299. 75: fprintf(stderr,
  24300. 76: "Error reading from %s\n", obj_name);
  24301. 77: return ERROR;
  24302. 78: }
  24303. 79:
  24304. 80: printf("******** Start of Menu #%d: *********\n", i+1);
  24305. 81: printf("\tTitle: %s\n", Menu.title);
  24306. 82: printf("\tPath: %s\n", Menu.path);
  24307. 83: printf("\tContains %d items.\n", Menu.nitems);
  24308. 84: printf("\talign = %c\n", Menu.align);
  24309. 85: printf("\tColumns = %d, ", Menu.columns);
  24310. 86: printf("Spacing = %d, Widest = %d\n",
  24311. 87: Menu.spacing, Menu.widest);
  24312. 88: printf("\tShell escapes are %sallowed",
  24313. 89: Menu.escape == YES ? "" : "NOT ");
  24314. 90: printf("\n");
  24315. 91:
  24316. 92: for (j = 0; j < Menu.nitems; j++)
  24317. 93: {
  24318. 94: if (fread((Void *) ip, sizeof (ITEM), 1, fp) != 1)
  24319. 95: {
  24320. 96: fprintf(stderr,
  24321. 97: "Error reading from %s\n", obj_name);
  24322.  
  24323. 98: return ERROR;
  24324. 99: }
  24325. 100: printf("Item #%d:\n", j+1);
  24326. 101: printf("\tTEXT = %s\n", ip->text);
  24327. 102: printf("\tPATH = %s\n", ip->path);
  24328. 103: printf("\tACTION = %s\n", ip->action);
  24329. 104: printf("\tHELP = %s\n", ip->help);
  24330. 105: printf("pre-clear = %c\n", ip->pre_clear);
  24331. 106: printf("post-clear = %c\n", ip->post_clear);
  24332. 107: printf("prompt = %c\n", ip->prompt);
  24333. 108: printf("acttyp = %d\n", ip->acttyp);
  24334. 109: printf("lmenunum = %d\n", ip->lmenunum);
  24335. 110: printf("nextcode = %d\n", ip->nextcode);
  24336. 111: printf("nextitem = %d\n", ip->nextitem);
  24337. 112: printf("\n");
  24338. 113: }
  24339. 114: printf("**** END OF MENU #%d ****\n", i+1);
  24340. 115: }
  24341. 116: return OK;
  24342. 117: }
  24343. /* End of File */
  24344.  
  24345.  
  24346.  
  24347.  
  24348.  
  24349.  
  24350.  
  24351.  
  24352.  
  24353.  
  24354.  
  24355.  
  24356.  
  24357.  
  24358.  
  24359.  
  24360.  
  24361.  
  24362.  
  24363.  
  24364.  
  24365.  
  24366.  
  24367.  
  24368.  
  24369.  
  24370.  
  24371.  
  24372.  
  24373.  
  24374.  
  24375.  
  24376.  
  24377.  
  24378.  
  24379.  
  24380.  
  24381.  
  24382.  
  24383.  
  24384.  
  24385.  
  24386. CUG New Releases
  24387.  
  24388.  
  24389. GNU C++ And Spell Checker
  24390.  
  24391.  
  24392.  
  24393.  
  24394. Kenji Hino
  24395.  
  24396.  
  24397. Kenji Hino is a member of The C User's Group technical staff. He holds a
  24398. B.S.C.S. from McPherson College and an undergraduate degree in metallurgy from
  24399. a Japanese university. He enjoys playing drums in a reggae band.
  24400.  
  24401.  
  24402.  
  24403.  
  24404. New Distribution Fees
  24405.  
  24406.  
  24407. The order blanks in the center of this magazine reflect a new fee schedule for
  24408. C Users Group Library Volumes. Under the new fee schedule, you will pay $4.00
  24409. per disk (not volume). If the volume fits on one disk, it will cost only $4.
  24410. If the volume spans three disks, it will cost $12. There is also a $3.50
  24411. shipping and handling fee paid for each order.
  24412. We've decided to make our fee schedule consistent with established practice in
  24413. user-supported software. Our previous "one price per volume" fees have caused
  24414. quite a bit of confusion. It's just too easy to compare the "per disk" rates
  24415. of other sources with our "per volume" rate and conclude that we're gouging
  24416. our customers. In fact, because so many of our volumes are multi-disk sets, we
  24417. expect the average order to change very little. The major effect will be to
  24418. decrease the price on orders which include several one-disk volumes and
  24419. increase the price of orders which include several multi-disk volumes.
  24420. Please examine the new order form and let us know if you have any questions or
  24421. if you have suggestions for improving it.
  24422.  
  24423.  
  24424. Update
  24425.  
  24426.  
  24427.  
  24428.  
  24429. CUG350 PCX Graphics Library
  24430.  
  24431.  
  24432. Ian Ashdown (CANADA) has updated his PCX Graphics Library, PCX_LIB. The new
  24433. version is 1.00C and corrects the following bugs:
  24434. 1. PCX_LIB didn't work properly when compiled under the 80x86 compact or large
  24435. model and the data segments exceed 64KB in size.
  24436. 2. The "usage" display of RD_DEMO and WR_DEMO didn't always display the full
  24437. text.
  24438.  
  24439.  
  24440. New Releases
  24441.  
  24442.  
  24443.  
  24444.  
  24445. CUG359 GNU C/C++ for 386
  24446.  
  24447.  
  24448. Written by Free Software Foundation, ported to DOS by DJ Delorie and submitted
  24449. by Henri de Feraudi (FRANCE) and Mike Linderman (CANADA), this package
  24450. contains a 32-bit 80386 DOS extender with symbolic debugger, a C/C++ compiler
  24451. with utilities, development libraries, and source code. It generates full 32-
  24452. bit programs and supports full virtual memory with paging to disk. The package
  24453. requires a 80386-based IBM compatible PC or PS/2. The 80387 emulator currently
  24454. does not emulate trancendental functions (exp, sin,.. etc). The software
  24455. requires approximately 4-5 MB of hard drive space is required. 640KB RAM is
  24456. required.
  24457. The following hardware is supported:
  24458. Up to 128MB of extended (not expanded) memory
  24459. Up to 128MB of disk space used for swapping
  24460. SuperVGA 256 color mode up to 1024x768
  24461. 80387
  24462. XMS & VDISK memory allocation strategies
  24463. V86 programs, QEMM, 386MAX, DesqView, Windows/386 are not supported.
  24464. The disk includes binary executable files: C/C++ compilers, LALR(1) parser
  24465. (bison), lexical parser (flex), C/C++ preprocessor, 80386/80387 assembler,
  24466. a.out (BSD) format linker (ld), archive utility, symbol stripper, compilation
  24467. coodinator, basic 32-bit DOS extender, symbolic debugger, etc.
  24468. In addition, libraries that support standard routines, math routines,
  24469. graphics, and mouse routines (These are compiled with gcc. The source code is
  24470. included in the disk.), include- header files, documentation, sources for
  24471. extender and various VGA/SuperVGA drivers, diffs from FSF distributions to
  24472. DOS-compatible, sources for the utilities, sample C++ sources using graphics
  24473. and mouse, and 80387 emulator for non- 80386 systems.
  24474. Due to the volume of files and DOS nature of programs, all files are archived
  24475. by PKZIP (unzip utility is also included) and the archived file is separated
  24476. into pieces by "split" utility. Thus, only MS-DOS disk format is distributed
  24477. from us.
  24478.  
  24479.  
  24480.  
  24481. CUG360 Uspell
  24482.  
  24483.  
  24484. Bill McCullough (MO) has contributed a spell checker program, Uspell. Uspell
  24485. is basically a modification of CUG217 Spell, however significantly optimized
  24486. to improve the performance under UNIX. The optimization techniques Uspell uses
  24487. include: replacing scanf with a single read, retaining the whole index in
  24488. memory, converting input words to five-bit format before spell checking,
  24489. reading the dictionary in increments of file system blocks caching locally,
  24490. eliminating stdio functions, etc.
  24491. The disk includes C source code for spell checker, ASCII text dictionary,
  24492. compressed dictionary and index files, and a utility used to convert the ASCII
  24493. text dictionary to compressed one.
  24494.  
  24495.  
  24496. CUG Directory III
  24497.  
  24498.  
  24499. The C Users' Group Directory III will be available the first of April. The
  24500. directory has capsuled descriptions of CUG 250 through 299. The first
  24501. (CUG100-199) and second (CUG 200- 249) volume of the directory are currently
  24502. available through R&D Publications, Inc.
  24503.  
  24504.  
  24505.  
  24506.  
  24507.  
  24508.  
  24509.  
  24510.  
  24511.  
  24512.  
  24513.  
  24514.  
  24515.  
  24516.  
  24517.  
  24518.  
  24519.  
  24520.  
  24521.  
  24522.  
  24523.  
  24524.  
  24525.  
  24526.  
  24527.  
  24528.  
  24529.  
  24530.  
  24531.  
  24532.  
  24533.  
  24534.  
  24535.  
  24536.  
  24537.  
  24538.  
  24539.  
  24540.  
  24541.  
  24542.  
  24543.  
  24544.  
  24545.  
  24546.  
  24547.  
  24548.  
  24549.  
  24550.  
  24551.  
  24552.  
  24553.  
  24554. Editor's Forum
  24555. I am in the midst of installing several new C and C++ compilers on my laptop.
  24556. The process already has me talking to myself. The world has sure changed in
  24557. the year I spent in Australia. (I bought little software then because it's a)
  24558. scarce and b) expensive Down Under.) These new packages are big.
  24559. I felt pretty smug a year ago buying a laptop with 6MB of RAM and 120MB of
  24560. hard disk. I divvied up the former among RAM drive and various flavors of
  24561. managed memory. I split the latter into six 20MB partitions, to provide a few
  24562. fire walls. It was hard to imagine hitting limits with all that real estate.
  24563. A large software package way back then ate up 3MB of disk. It might require
  24564. over a megabyte of RAM to run well. Such parameters pale beside modern
  24565. requirements, however.
  24566. I'm not even talking about loading libraries for every possible memory model
  24567. -- I usually stick with just small and large. No, it's all those Windows
  24568. support libraries that eat disk space. And the gazillions of support tools we
  24569. now take for granted. A 10MB package is typical these days. Some can be twice
  24570. that size. If you're a child of the Nineties, let me give you some
  24571. perspective. In early 1979, I was assured that few CP/M machines would ever
  24572. have more than 32KB of RAM and 256KB of diskette storage. (You couldn't write
  24573. enough assembly language to fill up the former. You couldn't afford two floppy
  24574. drives to go beyond the latter.) Luckily, I ignored those projections and
  24575. wrote C compilers for machines twice as large. The hardware was there when the
  24576. software demanded it.
  24577. My laptop now has 10MB of RAM. I use on-the-fly disk compression to double the
  24578. effective hard disk size. I'm looking into networking my PCs so I can have
  24579. faster file access to a really big disk.
  24580. Now there's only one problem remaining. I'm not sure that I'm writing any more
  24581. code with these wonderful new tools. Or any better code.
  24582. P.J. Plauger
  24583. pjp@plauger.uunet.uu.net
  24584.  
  24585.  
  24586.  
  24587.  
  24588.  
  24589.  
  24590.  
  24591.  
  24592.  
  24593.  
  24594.  
  24595.  
  24596.  
  24597.  
  24598.  
  24599.  
  24600.  
  24601.  
  24602.  
  24603.  
  24604.  
  24605.  
  24606.  
  24607.  
  24608.  
  24609.  
  24610.  
  24611.  
  24612.  
  24613.  
  24614.  
  24615.  
  24616.  
  24617.  
  24618.  
  24619.  
  24620.  
  24621.  
  24622.  
  24623.  
  24624.  
  24625.  
  24626.  
  24627.  
  24628.  
  24629.  
  24630.  
  24631.  
  24632.  
  24633.  
  24634.  
  24635.  
  24636. New Products
  24637.  
  24638.  
  24639. Industry-Related News & Announcements
  24640.  
  24641.  
  24642.  
  24643.  
  24644. Enhanced Supernova Available
  24645.  
  24646.  
  24647. Four Seasons Software has begun shipping SuperNOVA v2.2. This version
  24648. introduces a business graphics feature for SuperNOVA v2.2. The business
  24649. graphics feature allows developers to compile and display data from different
  24650. databases in a variety of graphical formats including scanned images, graphs,
  24651. bar and pie charts. This feature allows business application developers, MIS
  24652. managers and even end-users to benefit from SuperNOVA's database independence,
  24653. which allows data to be shared across most platforms and operating systems.
  24654. One of SuperNOVA's capabilities is its ability to run in a distributed
  24655. processing mode. Using an optional distributed processing capability,
  24656. applications developed using SuperNOVA can run on a network consisting of
  24657. various system types as well as different proprietary DBMS and operating
  24658. systems. Running in the distributed mode, the complete network is available as
  24659. one virtual computer system.
  24660. SuperNOVA v2.2 enhances the distributed processing mode by adding an Optimized
  24661. Packet Handling feature. Applications can now transparently access data and
  24662. processing routines at high speeds between network nodes. The network can be
  24663. comprised of VMS, UNIX, and MS/DOS machines, even MS/DOS systems with only
  24664. 640KB of memory. SuperNOVA v2.2 supports access to Teradata databases along
  24665. with access to a range of other commercial Data Base Management Systems
  24666. (DBMS), such as Informix, Oracle, Ingres, and Sybase, as well as flat files.
  24667. Utilizing SuperNOVA's fourth generation language, applications can
  24668. simultaneously integrate data from more than one DBMS and can run on a variety
  24669. of operating systems including DOS, UNIX and VMS.
  24670. SuperNOVA v2.2 ranges in price from $995 to $115,000 depending on the hardware
  24671. platform. Site licensing is available. For more information contact Four
  24672. Seasons Software, 2025 Lincoln Highway, Edison, NJ 08817, (908) 248-6667, Fax
  24673. (908) 248-6675.
  24674.  
  24675.  
  24676. Borland Ships Three Object-Oriented Software Tools For Microsoft Windows
  24677.  
  24678.  
  24679. Borland International, Inc. announced today the availability of three software
  24680. development products that boost programmer productivity and accelerate the
  24681. development and maintenance of software programs for Microsoft Windows and the
  24682. DOS operating systems. The products which feature object-oriented C++, in
  24683. addition to the C programming language, are: Turbo C++ for Windows 3.0,
  24684. Borland C++ 3.0, and Borland C++ & Application Frameworks 3.0.
  24685. Turbo C++ for Windows 3.0 offers an inexpensive route to Windows programming
  24686. for entry level programmers. Major features include a graphical user
  24687. interface, an object-oriented foundation that provides built-in objects to
  24688. save developers time and coding, a library that enables DOS-based C and C++
  24689. programs to run instantly under Windows, and Borland's resource editor, a tool
  24690. for visually creating Windows resources such as icons, dialogs, fonts, and
  24691. bitmap graphics without writing code. Borland's C++ v3.0 compiler now supports
  24692. global optimization. Tools available in Borland C++ 3.0 include an
  24693. object-oriented assembler; Turbo Debugger for tracking down bugs; Turbo
  24694. Profiler for Windows and DOS for locating bottlenecks in programs; and
  24695. WinSight, for monitoring Windows messages.
  24696. Borland C++ & Application Frameworks 3.0 is the high-end product of Borland's
  24697. C++ product line. It includes everything in Borland C++ 3.0, plus
  24698. Object-Windows and Turbo Vision application frameworks, complete with source
  24699. code for the frameworks and the runtime library. Borland's application
  24700. frameworks define a standard user interface and behavior for Windows (using
  24701. ObjectWindows) or DOS (using Turbo Vision). The application frameworks give
  24702. programmers a head start toward creating applications by providing
  24703. user-interface building blocks, fundamental data structures, and support for
  24704. object-oriented programming.
  24705. Turbo C++ for Windows 3.0 carries a suggested retail price of $149.95. Borland
  24706. C++ 3.0 has suggested a retail price of $495 and Borland C++ & Application
  24707. Frameworks 3.0 carries a suggested retail price of $749. All products are
  24708. available now through major resellers or direct through Borland.
  24709. Current users of Turbo C++ and Turbo C can upgrade to Turbo C++ for Windows
  24710. 3.0 for $89.95. Turbo Pascal users can upgrade to Turbo c++ for Windows 3.0
  24711. for $99.95. For owners of Borland C++ 2.0 and Turbo C Professional, it will be
  24712. $125 to upgrade to Borland C++ 3.0 and $199.95 to upgrade to Borland C++ &
  24713. Application Frameworks 3.0. Borland C++ & Application Frameworks 2.0 users
  24714. will automatically receive Borland C++ & Application Frameworks 3.0 free of
  24715. charge. All prices, including the upgrade and offers, are in U.S. dollars and
  24716. apply only in the U.S. and Canada.
  24717. For more information contact Borland, 1800 Green Hills Rd., P.O. Box 660001,
  24718. Scotts Valley, CA 95067-0001, (408) 439-4825.
  24719.  
  24720.  
  24721. Softaid Introduces 80186EB Emulator
  24722.  
  24723.  
  24724. Softaid has released an In-Circuit Emulator for Intel's 80186EB
  24725. macroprocessor. The UEM is composed of two tightly integrated parts: a full
  24726. featured emulator, and Softaid's Source Level Debugger. The emulator hardware
  24727. gives both EEs and firmware engineers the resources needed to debug an
  24728. embedded system; the Source Level Debugger (SLD) wraps a shell around the
  24729. emulator that gives the user full control over C and PL/M debugging. It
  24730. includes 131,072 hardware breakpoints, each of which can be set on virtually
  24731. any condition or machine cycle type. The user can nest breakpoint conditions
  24732. up to five levels deep to find particularly devilish bugs.
  24733. The UEM's real time trace collects machine cycles as the target program
  24734. executes. The source level debugger shows trace data in the original source
  24735. form, so the programmer can debug in the same context he wrote the code.
  24736. The UEM does come with a real time performance analyzer. The SLD automatically
  24737. couples this to the C code, assigning one function per performance bin (255
  24738. maximum). A few seconds of work identifies time-critical routines that must be
  24739. optimized. All UEM-Series emulators come with a memory access monitor that
  24740. non-intrusively monitors all memory references. The user can specify areas of
  24741. memory as being write protected, read and write protected, fetch protected
  24742. (i.e., no fetches allowed), or open for any access. A breakpoint will occur if
  24743. the mode is violated, immediately identifying code or pointers that "wander
  24744. off." The 80186EB UEM costs $7500 and is available from stock. For more
  24745. information contact Softaid, Inc., 8300 Guilford Road, Columbia, MD 21046,
  24746. (410) 290-7760 or (800) 433-8812.
  24747.  
  24748.  
  24749. Ethernet Starter Kit
  24750.  
  24751.  
  24752. Crystal Computing Corporation has released an Ethernet starter kit, FreeLAN-E,
  24753. which is suitable for small business or home office with 2 to 16 computers.
  24754. With FreeLAN, the user can share printers/ plotters, share files, and run
  24755. network version software at 10 Mbps speed. Starting from two users, FreeLAN
  24756. can be easily upgraded to up to 256 users. And since it is peer-to-peer
  24757. service, any machine on the FreeLAN network can share files or
  24758. printers/plotters with any other users on the network. This Starter Kit
  24759. package includes two 10 Mbps Ethernet cards, on e15' coax cable, two 50 ohm
  24760. terminators, and Network Operating System for two users, featuring file
  24761. sharing, file record locking, printer sharing/spooling, spool management and
  24762. password protection. Furthermore, FreeLAN supports Novell Netware and Windows
  24763. 3.0. It is NetBIOS based with NetBIOS included. FreeLAN supports IBM
  24764. PC/XT/AT/386/486 and 100% compatibles, under MS-DOS 3.1 or later. FreeLAN
  24765. starter kit is immediately available from Crystal Computing Corporation. The
  24766. U.S. suggested retail price for FreeLAN-E, the Ethernet version, is $249. And
  24767. the U.S. suggested list price for FreeLAN-A, the FreeLAN Arcnet starter kit,
  24768. is $179.
  24769. For more information contact Paul Chen, 3140 De La Cruz Blvd, Suite 200, Santa
  24770. Clara, CA 95054, (408) 748-0685, (408) 748-0125, Fax (408) 748-0879.
  24771.  
  24772.  
  24773. Embedded SQL for Microsoft Visual Basic
  24774.  
  24775.  
  24776. Quadbase Systems Inc. has started shipping a new version of Quadbase-SQL/Win
  24777. which includes a new library that allows Visual Basic programmers to use
  24778. Embedded SQL to access the Quadbase's SQL engine under Microsoft Windows.
  24779. Essentially, Embedded SQL allows interspersing of SQL and host programming
  24780. language (in this case Visual Basic) statements. Called VBESQL, this new
  24781. library supports ANSI standard Embedded-SQL syntax plus extensions such as
  24782. scroll cursors and host variables of user-defined data types. An embedded SQL
  24783. preprocessor for C is already included as a standard component of the product.
  24784. Quadbase-SQL/Win is a full featured relational database engine in the form of
  24785. a DLL that supports various Windows development languages such as Visual
  24786. Basic, C, C++, Smalltalk/V Windows, ACTOR, SQLWindows etc. It fully supports
  24787. ANSI SQL level 2 standard plus extensions offering advanced features such as
  24788. referential integrity, outer join, multi-user concurrency control etc. The DLL
  24789. can support multiple instances. It is very fast and compact. The underlying
  24790. file formats are dBASE compatible. It is ideal for small to medium sized LAN
  24791. file server systems or standalone or laptop machines. Users can benefit from
  24792. advanced relational database features and be provided with migration path to
  24793. SQL servers.
  24794. The system requires an IBM or compatible 286, 386, or 486 PC with hard disk, a
  24795. minimum of 2MB of RAM, and Windows 3.0. For more information contact Quadbase
  24796. Systems, (408) 738-6989, Fax (408) 738-6980.
  24797.  
  24798.  
  24799. Quality Assurance From Softran
  24800.  
  24801.  
  24802. C-Verify is a quality assurance tool which proves that your test suite has
  24803. taken every possible path through your C-language program.
  24804. All source modules which are compiled into the executable program are passed
  24805. through the C-Verify preprocessor. C-Verify analyzes the program's logic and
  24806. generates an index file containing a unique label reference for each logic
  24807. branch. C-Verify also outputs a new version of the source containing function
  24808. calls which generate a file list of all the logic branches taken when the
  24809. executable is run.
  24810. The source output by C-Verify is compiled and run. As the program executes,
  24811. the output file is built from the C-Verify label references for each logic
  24812. branch taken during the run. Many different tests may be executed, passing the
  24813. resulting logic probes into the output file for analysis.
  24814.  
  24815. When the test suite is finished, C-Verify compares the file containing all
  24816. possible logic branches with the file containing branches actually taken,
  24817. identifying which logic has and has not been covered by the test suite. The
  24818. programmer may then devise additional tests which pass through the neglected
  24819. logic and run C-Verify again until 100% coverage has been attained. For more
  24820. information contact Softran Corporation, One Naperville Plaza, Naperville, IL
  24821. 60563, (800) 462-3932, Fax (708) 505-3457.
  24822.  
  24823.  
  24824. C Development Environment
  24825.  
  24826.  
  24827. Interactive Development Environments, Inc. (IDE) of San Francisco, California,
  24828. has released an upgrade of the C Development Environment. IDE has added
  24829. reverse engineering and code generation modules, which provide the capability
  24830. to synchronize code and design. IDE also has improved facilities to query the
  24831. shared repository and navigate easily though designs, code, and documentation.
  24832. Using the C Development Environment, developers can reverse engineer existing
  24833. C source code into designs in the form of specifications and graphical
  24834. representations, or start with designs and generate C source code. The C
  24835. Development Environment maintains consistency between designs and code by
  24836. incrementally updating one to reflect changes in the other. This product
  24837. significantly expands IDE's market, since it now offers solutions to
  24838. development teams working on existing systems as well as to developers who
  24839. work primarily at the source code level. For more information call IDE, 595
  24840. Market St., 10th floor, San Francisco, CA 94105, (415) 543-0145, or
  24841. 0483-579000 (U.K.), FAX (415) 543-0145.
  24842.  
  24843.  
  24844. EMS Expanded Utility Library
  24845.  
  24846.  
  24847. EMS Professional Shareware Libraries has released an expanded version of the
  24848. dBUtility Library. Nearly 300 programs have been added to the library,
  24849. bringing the total to 2000 different Public Domain and Shareware utilities
  24850. designed specifically for Dbase language developers (including dBASE III/IV,
  24851. FoxPRO, Clipper, QuickSilver, etc.) by over 1500 US and international firms.
  24852. All files are compressed using PKZIP, but still fill 122 360KB or 31 1.44MB
  24853. diskettes with over 110MB of material. The product includes a database index
  24854. and search program for these and 900 additional commercial Dbase related
  24855. utility programs, allowing searches by name, type, vendor, and free text
  24856. searches. When programmers need to locate a particular type of Dbase utility
  24857. or code routinne, they can find it without having to "reinvent the wheel". The
  24858. library is $149 plus S/H. For further information contact EMS 4505 Buckhurst
  24859. Ct., Olney MD 20832, (301) 924-3594, FAX (301) 963-2708.
  24860.  
  24861.  
  24862. Teamone Systems Upgrades Teamnet
  24863.  
  24864.  
  24865. TeamOne Systems, Inc. has released an upgrade of its TeamNet concurrent
  24866. engineering environment for distributed configuration management. TeamNet 3.0
  24867. includes a new X Window graphical interface that significantly enhances
  24868. ease-of-use, and improved process control and conflict resolution mechanisms
  24869. for concurrent development. The new release also includes major performance
  24870. upgrades over TeamNet 2.0 for configuration management function, a floating
  24871. license facility, and enhanced NFS client access.
  24872. TeamNet is a UNIX-based configuration and data management system that supports
  24873. software, electronic, and mechanical engineering projects. TeamNet provides
  24874. configuration management and version control by transparently tracking product
  24875. development using any tool that runs on any platform on an NFS-based network.
  24876. New tools can be immediately integrated into the TeamNet environment with no
  24877. need to modify or encapsulate tools prior to use.
  24878. TeamNet 3.0 features an enhanced OPEN LOOK graphical interface, an enhanced
  24879. conflict resolution process, and two-phase commit mechanism for checking in
  24880. changes and managing work area and baseline updates. Network enhancements
  24881. include virtual copy capability and a floating license system.
  24882. In networked environments TeamNet licenses are priced at $3,000 and will
  24883. support an average of two concurrent users per license. For more information
  24884. contact TeamOne Systems, Inc., (408) 730-3500.
  24885.  
  24886.  
  24887. Videotex Ships Upgraded Imaging Library
  24888.  
  24889.  
  24890. Version 3 of T-BASE, the advanced picture and document imaging library from
  24891. Videotex Systems, Inc., has been released. Bundled with T-BASE is ChromaTools,
  24892. an advanced color manipulation and image conversion utility. ChromaTools is
  24893. also from Videotex Systems, Inc. T-BASE allows developers to add pictures and
  24894. document images to their database management applications written in C, C++
  24895. and virtually every Xbase dialect. T-BASE supports any image in the PCX file
  24896. format. Version 3 of T-BASE incorporates several new features never before
  24897. available in an Xbase imaging library: Graphics @SAY and @GET commands,
  24898. Automatic Image scaling, Image scaling on-the-fly, Support for 1024x768
  24899. monitors with 256 colors, Automatic color correction for multiple VGA images
  24900. with different palettes. Because it is 100% royalty-free, T-BASE is the most
  24901. cost-effective imaging library on the market. Once you buy T-BASE, you are
  24902. free to use T-BASE to develop and sell as many applications as you want.
  24903. T-BASE is also hardware independent, so it does not limit you to certain types
  24904. of video cards or other equipment. T-BASE automatically detects your hardware
  24905. connfiguration and adjusts itself to work in that configuration. No other
  24906. Xbase imaging library offers this level of hardware independence. ChromaTools,
  24907. which is bundled with T-BASE, is an advanced color manipulation and image
  24908. conversion utility that makes it easy to convert images in a variety of file
  24909. formats into a single format for inclusion in desktop presentations, CD ROM
  24910. libraries, picture databases, demo disks, bulletin boards, advertising, kiosks
  24911. and more. ChromaTools is a $249 value. T-BASE has a suggested retail price of
  24912. $495 and is available from dealers nationwide as well as from Videotex
  24913. Systems, Inc. For more information contact Videotex Systems, Inc., 8499
  24914. Greenville Ave., Suite 205, Dallas, Texas 75231, (800) 888-4336 or (214)
  24915. 343-4500, FAX (214) 348-3821.
  24916.  
  24917.  
  24918. High-Performance 80486-Based Eisa Single Board Computer
  24919.  
  24920.  
  24921. Diversified Technology has introduced the ESP2000, an 80486 based EISA system
  24922. processor. This processor is based on a passive backplane architecture and
  24923. incorporates Intel's latest 82350DT chip set. Operating at 33MHz, the ESP2000
  24924. utilizes a CPU-to-memory protocol which allows the memory subsection to
  24925. operate independently of the CPU clock. This protocol also insures a smooth
  24926. transition to 50MHz board options planned for future release.
  24927. The ESP2000's high performance is due largely to the internal cache in the
  24928. 80486. In addition to the internal cache, the ESP2000 provides a local memory
  24929. architecture that consists of four-way interleaving and 128-bit "burst"
  24930. accesses to provide near zero wait state performance. Up to 64MB of system
  24931. memory is supported and this memory is field upgradable accepting 256KB,
  24932. 512KB, 1MB and 2MB by 36 SIMM modules.
  24933. The ESP2000 also provides support for an optional Intel Turbo cache module.
  24934. Both 64KB and 128KB modules are supported. The architecture implemented is a
  24935. "parallel" cache; thus, it resides in parallel with the CPU and local DRAM. As
  24936. a result, local DRAM cycles can by initiated in parallel with cache TAGRAM
  24937. lookup, precluding any penalty typically associated with a cache lookup.
  24938. This single board computer's bus operates at the EISA standard and ISA
  24939. backward compatible bus speed of 8.33MHZ. The EISA bus DMA channels can
  24940. operate in both eight- and 16-bit modes with EISA/ISA bus slaves and masters,
  24941. and 32 bit modes with EISA slaves and master. The interface of EISA masters to
  24942. memory is optimized such that the memory is capable of sustaining the full 33
  24943. megabytes per second bandwidth as defined in the EISA specification.
  24944. Combined with one of Diversified Technologies' EBP series of EISA backplanes,
  24945. the ESP2000 provides the EISA designer with a system capable of supporting six
  24946. intelligent EISA bus masters simultaneously for demanding applications. To
  24947. ensure compliance with FCC and Underwriters Laboratory regulations, the
  24948. ESP2000 incorporates extensive design measures for EMI/RFI emissions control
  24949. and U.L. safety requirements.
  24950. The 33MHz EISA "486" ESP2000 without RAM is priced at $2,595 in single piece
  24951. quantities. Allow 1 to 4 weeks delivery ARO. For further information contact
  24952. Diversified Technology Inc., (800) 443-2667.
  24953.  
  24954.  
  24955. Upgrade Of C Library
  24956.  
  24957.  
  24958. Sequiter Software Inc., announced the release of CodeBase 4.5, a multi-user,
  24959. database management library for C, C++, Visual Basic and Turbo Pascal for
  24960. Windows. Programmers can now work directly with the data, index and memo files
  24961. of dBASE, FoxPro, and Clipper from DOS or Microsoft Windows. The latest FoxPro
  24962. 2.0 and dBASE IV index file formats are now supported.
  24963. CodeBase 4.5 gives C/C++ programmers the ability to enhance the data entry
  24964. capabilities of Microsoft Windows with entry validation and picture templates.
  24965. CodeBase 4.5 database browse and edit windows can be designed using any
  24966. resource toolkit. The CodeBase 4.5 library may also be used as a royalty free
  24967. DLL.
  24968. CodeBase 4.5, with complete multi-user source code and a 90 day money back
  24969. guarantee, lists at US$395. For more information contact Sequiter Software
  24970. Inc. #209, 9644-54 Ave., Edmonton, AB, Canada, T6E 5V1, (403) 437-2410, FAX
  24971. (403) 436-2999.
  24972.  
  24973.  
  24974. Object Professional For C++
  24975.  
  24976.  
  24977. TurboPower Software announces Object Professional for C++, a library of
  24978. textmode user interface objects. Object Professional for C++ (OPC) is a
  24979. straight port of the user interface objects from Object Professional for Turbo
  24980. Pascal.
  24981. OPC includes high-level objects such as text editors, dialog boxes, scrolling
  24982. data entry screens, help systems, and more. The user interfaces are
  24983. ready-to-use, and built-in calls allow programmers to customize the behavior.
  24984. OPC does not use event-driven programming. Applications built using OPC will
  24985. run nicely in 640KB 8088-based PCs. OPC includes utilities to help build menu
  24986. systems and data entry screens. The utilities support interactive design and
  24987. testing and then automatically generate source code.
  24988. OPC includes full source code, 1,300 pages of documentation, pop-up help, and
  24989. plenty of example and demo programs. No payment of royalties is required. OPC
  24990. requires Borland C++ 2.0 or 3.0. Object Professional for C++ costs $249. For
  24991. futher information contact TurboPower Software, P.O. Box 49009, Colorado
  24992. Springs, CO 80949-9009, (800) 333-4160, FAX (719) 260-7151.
  24993.  
  24994.  
  24995. New Server For OS/2
  24996.  
  24997.  
  24998.  
  24999. Laurel, MD-XDB Systems, Inc., has released an upgrade of their SQL Engine for
  25000. OS/2, the XDB-SERVER. With the XDB-SERVER for OS/2 2.41, DB2 applications
  25001. become portable to client-server platforms. Since the XDB-SERVER is network
  25002. independent, DB2 compatibility can exist on multiple platforms and operating
  25003. systems.
  25004. Multiple physical disk volumes are now supported. Now developers can utilize
  25005. multiple disk volumes to separate various data objects like indices and
  25006. databases thereby decreasing the amount of time required to retrieve data. The
  25007. size of a database is also no longer limited by the size of one OS/2 disk
  25008. volume. In addition, the XDB-SERVER for OS/2 version 2.41 has enhanced its
  25009. query optimization.
  25010. XDB's SQL Engine provides the same mainframe quality implementation as DB2
  25011. including referential integrity, record level locking, rollback and recovery,
  25012. field level security, etc. The XDB-SERVER is built using the client-server
  25013. architecture. The XDB SERVER for OS/2 supports both the Named Pipes and
  25014. Netbios protocols.
  25015. The price of the XDB-SERVER for OS/2 version 2.41 is $2495. For more
  25016. information contact XDB Systems, Inc., 14700 Sweitzer Lane, Laurel, MD
  25017. 20707-2921; (301) 317-6800.
  25018.  
  25019.  
  25020. Database Application Generator
  25021.  
  25022.  
  25023. Novato-based software developer Kedwell has just released a new version of its
  25024. database application generator, DataBoss. Unlike database management systems
  25025. such as dbase IV and Paradox, DataBoss doesn't have it's own language. To
  25026. create applications you team it up with a Pascal, C, or C++ compiler. While
  25027. this can make some things more complex, it also makes DataBoss very flexible.
  25028. If DataBoss doesn't give you quite what you want you can add your own code or
  25029. use library functions to add extra functionality. it also makes it very easty
  25030. to port DataBoss applications to other platforms, and since you're delivering
  25031. a compiled program you don't have to worry about run-time or licenses.
  25032. DataBoss supports Borland's Turbo Pascal v5.0, v5.5, and v6.0; Borland C++;
  25033. Turbo C v2.0, Turbo C++; and Microsoft's C compilers v5.1 and v6.0. It can
  25034. also be used with Microsoft QuickC compilers but the integrations isn't as
  25035. tight. DataBoss can be used to create mutliuser LAN-based applications as well
  25036. as single-user applications. It supports Novell, 3Com, Tops, 10-Net and PC-MOS
  25037. systems.
  25038. DataBoss creates applications that use B-tree indexed files, which are
  25039. reorganized whenever the database is updated so as to reduce data access
  25040. times. Because it uses the B-tree indexes and because it is compiled, a
  25041. DataBoss application is generally faster than a similar application developed
  25042. using DBMS system.
  25043. For more information contact Kedwell Software, 7460 Redwood Boulevard, Suite
  25044. A, Novato, CA 94945, (415) 899-8525, FAX(415) 899-8524.
  25045.  
  25046.  
  25047. Upgraded Library From Friendly Solutions
  25048.  
  25049.  
  25050. The Friendly Solutions (TFS) Company has released their software package,
  25051. C*DRIVE v1.1. C*DRIVE v1.1 has added features such as extensive validity
  25052. checking for data entry in the 'GET' system, mouse capabilities incorporated
  25053. into the menu system, virtual shadows for drawing boxes, complete mouse
  25054. module, function for scrolling arrays, pop-up calculator, and pop-up calendar.
  25055. The source code for whole library is included, rolyalty free. For further
  25056. information contact The Friendly Solutions, 6309 Chimney Woods Ct, Alexandria,
  25057. VA 22036, (703) 765-0654.
  25058.  
  25059.  
  25060. Announcing Softcode Version 2.5
  25061.  
  25062.  
  25063. Bottleworks Development Corporation has recently started shipping version 2.5
  25064. of its template-driven code generator, SoftCode. SoftCode includes a
  25065. menu-driven screen editor that allows developers to visually create customized
  25066. field definitions and user interfaces. SoftCode's screen editor is designed to
  25067. allow developers to create full working prototypes quickly and easily.
  25068. Enhancements to the screen editor in version 2.5 include the ability to
  25069. customize the screen editing environment and create windows; enhanced field
  25070. validation, and the ability to select and use fields and screens from external
  25071. programs.
  25072. The template sets available for Soft-Code 2.5 have also been significantly
  25073. enhanced. Templates are language-specific: there are separate templates for C,
  25074. Basic, Pascal, dBASE IV, Clipper, FoxPro, Arago and Quicksilver/dBXL, each of
  25075. which takes developers to generate complete multi-file applications including
  25076. pop-up picklist menus, pop-up trigger windows and context-sensitive help. In
  25077. addition, the Xbase-specific templates support scrollable, multi-record views.
  25078. Each template includes on-line help and comes with source code which can be
  25079. modified.
  25080. SoftCode 2.5 retails for $295 and includes one template set and a 30-day
  25081. guarantee. Additional templates retail for $149 each. For further information
  25082. contact Bottleworks Development Co., 333 Hempstead Avenue, Suite 213,
  25083. Malverne, N.Y 11565, (516) 596-9700, FAX (516) 596-9701.
  25084.  
  25085.  
  25086.  
  25087.  
  25088.  
  25089.  
  25090.  
  25091.  
  25092.  
  25093.  
  25094.  
  25095.  
  25096.  
  25097.  
  25098.  
  25099.  
  25100.  
  25101.  
  25102.  
  25103.  
  25104.  
  25105.  
  25106.  
  25107.  
  25108.  
  25109.  
  25110.  
  25111.  
  25112.  
  25113.  
  25114.  
  25115.  
  25116.  
  25117.  
  25118.  
  25119. We Have Mail
  25120. Dear Dr. Plauger,
  25121. Do we get our money back for the repeated article on circular lists? Or is
  25122. this a readership-attention-span-survey stunt?
  25123. Regards,
  25124. John Wheater
  25125. Surrey, England
  25126. You mean we didn't double your pleasure, double your fun?
  25127. Seriously, we have received several such letters, and would like to say thank
  25128. you all for the kindly pokes at our snafu. We promise to remove all the egg we
  25129. are wearing because of it, so that we are more recognizable in the future. --
  25130. dt
  25131. Dear Mr. Plauger:
  25132. You mentioned in The C Users Journal (October 1991) that you have the Turbo
  25133. C++ function library but since it is a licensed product you would not pass it
  25134. on or encourage anyone else to do so! I develop and market a C++ class library
  25135. with source and I am so glad when one of the most influential person of the
  25136. C/C++ community shows its support for honest practices. Thanks a thousand
  25137. times.
  25138. Patrick Nicolas
  25139. Network Integrated Services, Inc.
  25140. 221 West Dyer Road
  25141. Santa Ana, CA 92707-3426
  25142. I'm not completely altruistic. I've earned most of my money selling software
  25143. that is easily pirated. I am counting on a certain amount of income from
  25144. selling copies of the code from The Standard C Library. Still, I like to think
  25145. that I'd encourage honesty even without a personal stake in the matter. -- pjp
  25146. Dear Mr. Ward:
  25147. Listing 2 in your article "Debugging Instrumentation Wrappers for Heap
  25148. Functions" (CUJ, October 1991) caught my eye. I've had very similar problems
  25149. regarding arithmetic on void pointers, indeed with any sort of manipulations
  25150. using void pointers. One experience in particular dealt with the user-supplied
  25151. comparison function expected by qsort, which takes two (void *) pointers as
  25152. arguments.
  25153. I needed a comparison between two struct objects. My solution was a kludge fix
  25154. very similar to yours -- I defined two automatic pointer variables of the type
  25155. I needed in the comparison function for qsort My compiler (Turbo C++) wouldn't
  25156. allow me to do the comparison using the (void *) arguments to the comparison
  25157. function directly, even with casting to (struct type *). I had to assign both
  25158. arguments to the new pointers, then use them to do the comparison. Using
  25159. something like strcmp as the comparison function, in contrast, presents no
  25160. problem because strcmp takes two (void *) arguments in the first place.
  25161. When studying your Listing 2, a solution to the kludge struck me. Simply
  25162. redefine your my_free function and its redirection macro as
  25163. void my_free( char * tree )
  25164. #define free(x) my_free( (char *) x ),
  25165. instead of using a (void *) pointer argument to my_free. You can then do
  25166. pointer arithmetic directly on the (char *) argument passed to my_free. No
  25167. explicit casting to (char *) is needed when calling free in source code since
  25168. the redirection macro does this automatically. I suspect that something
  25169. similar could be done with my_alloc to avoid the same kludge.
  25170. I wouldn't be surprised if you've already thought of this one. Thanks for a
  25171. great article, which indirectly helped to cast (pun not really intended) some
  25172. light on my problem with . I'm not aware of any previous discussions in CUJ
  25173. regarding this.
  25174. Sincerely,
  25175. Tom Nelson
  25176. 5004 W. Mt. Hope Rd.
  25177. Lansing, MI 48917
  25178. Call it a kludge if you will. That's the sort of thing that X3J11 expected
  25179. programmers to do when it changed the declaration of the comparison function
  25180. for qsort. Honesty in type checking sometimes has its price. -- pjp
  25181. Thanks for the feedback. I'm glad you found it useful. -- rlw
  25182. Dear Sir:
  25183. With reference to your call for papers on Numerical Applications, I would like
  25184. to suggest two topics on which I have been working.
  25185. In view of your inability to fill orders for your new book on the C library,
  25186. articles which follow on those topics should prove interesting to your
  25187. readers. I have followed up on the criticisms which I made of the sin and exp
  25188. functions which appeared in CUJ, and found in fact that I was able to use the
  25189. ideas published while fixing the deficiencies and get results better than I
  25190. have seen elsewhere. I ordered the book and disk when they were first
  25191. advertised. I spoke to your book department today and they say it will be more
  25192. months before the book becomes available but they will try to ship the disk
  25193. alone. In this case I propose to go through the math functions and show where
  25194. they can be improved, and also where a slight variation on the code will
  25195. improve performance without affecting accuracy on pipelined processors. I
  25196. expect to have an HP720 available soon, in addition to SPARC, VAX, and 68000
  25197. available now.
  25198. In line with Ron Irvine's letter, I could write an article on how to
  25199. specialize C so that standard compilers generate as efficient code as would be
  25200. obtained by a FORTRAN compiler. This involves some uses of the const keyword
  25201. which are mainly theoretical, as current compilers don't seem to be using it
  25202. for optimization, as well as situations in which making a local copy of an
  25203. argument which is accessed through a pointer will enable a compiler to perform
  25204. the same optimizations which would be possible in FORTRAN. For recognition of
  25205. common subexpressions, the same rules now are followed by several compilers,
  25206. including gcc, Sun4, and IBM RS6000, so useful recommendations can be made to
  25207. permit this optimization.
  25208. Sincerely yours,
  25209. Dr. Timothy C. Prince
  25210. 452 Palmitas St
  25211. Solana Beach CA 92075-2046
  25212. Dr. Prince is one of the people I cited in The Standard C Library who
  25213. demonstrably know more about math functions than I do. He has sent me several
  25214. errata and suggestions for improvement, for which I am deeply grateful. I'm
  25215. sure that CUJ will be publishing more articles where Dr. Prince illuminates
  25216. the murkier corners of computer math.
  25217. By the way, I believe that books and code disks are going out the door
  25218. smoothly now. -- pjp
  25219. Just for the record... We didn't publish the book. The delays have been the
  25220. result of our difficulty in getting timely shipments from the publisher. --
  25221. rlw
  25222. Dear Sir:
  25223. With reference to Ron Irvine's letter, I have written an article on the
  25224. technical merits of C vs FORTRAN, but I don't know whether it will be
  25225. published. It went out on Usenet in an earlier version, but I haven't found a
  25226. way to access the net since I moved. This article expects the reader to be as
  25227. familiar with FORTRAN as with C, and turned out to be fairly long. To
  25228. summarize, the only situation of which I am aware in which ANSI C does not
  25229. have the potential to equal the performance of FORTRAN is where a function has
  25230. two or more in-out arguments.
  25231. FORTRAN compilers can be implemented quite successfully by automatic
  25232. translation to C, but the code required is not always a style which is natural
  25233. to use in C. I have encountered codes which were written in FORTRAN with no
  25234. intent of translation to C, which run faster when certain subroutines are
  25235. translated automatically to C and compiled with gcc than when they are
  25236. complied with a FORTRAN complier on a SPARC, because of gcc's better behavior
  25237. when faced with a shortage of pointer registers. If programs were coded in C
  25238. without using (intentionally or not) the full generality of the language,
  25239. current compilers could generate equally fast code in C or FORTRAN. This is
  25240. assuming that programmers and compilers take full advantage of ANSI C,
  25241. including use of features such as the const qualifier; otherwise much C code
  25242. cannot be optimized on a modular basis.
  25243. The other side of this answer is that the C programmer needs more knowledge
  25244. than the FORTRAN programmer, to avoid errors which FORTRAN does not permit, as
  25245. well as to write optimizable code.
  25246. Sincerely yours,
  25247. Dr. Timothy C. Prince
  25248. 452 Palmitas St.
  25249. Solana Beach, CA 92075-2046
  25250. C is often better than FORTRAN, but sometimes it is irretrievably worse. Both
  25251. Cray Research and the Numerical C Extensions Group have educated me at length
  25252. on this topic. Writers of Standard C compilers must catch up with three
  25253. decades of determined numerical optimization in FORTRAN compilers. And C lets
  25254. you do far more with pointers than FORTRAN permits with subscripts. Some
  25255. optimizations are just not safe in C, at least not without additional semantic
  25256. hints.
  25257. Dr. Prince is again correct, however. The vast majority of FORTRAN programs
  25258. (and programmers) would be better off in C. You must keep the numerical savvy
  25259. of the FORTRAN community and adopt the coding style of C to profit completely
  25260. from the transition. -- pjp
  25261. Dear Mr. Plauger:
  25262. I would like to present my own response to Ron Irvine, who asked (CUJ,
  25263. November 1991) why FORTRAN is the preferred language for numerical
  25264. applications.
  25265. I am a multi-language programmer, and one-half of a team who currently writes
  25266. and maintains a scientific analysis library in both C and FORTRAN. This
  25267. library is compiled across many platforms (Vax, PC, MacIntosh, etc.). My great
  25268. age and experience force me to speak out.
  25269. I began serious scientific programming in FORTRAN in 1963, when I was asked to
  25270. provide the surface temperature of Mars at the positions and times where the
  25271. radio signal from a space probe grazed its surface during an occultation.
  25272. Since that time I have programmed in many languages, and even taught a little
  25273. programming to my undergraduate physics students.
  25274. The power of FORTRAN lies in the numerous, robust mathematical function
  25275. libraries intrinsic to all FORTRAN compilers. The limitations of FORTRAN are
  25276. string handling and I/O.
  25277. C will do scientific analysis as fast as FORTRAN. I have written the same
  25278. scientific program in both languages. When compiled, you can't tell which is
  25279. which. In FORTRAN, I struggle with string handling and I/O. In C, I struggle
  25280. with data type conversion, and additionally must test for underflow, etc.
  25281. "The tremendous power of C pointers" is part of the litany of C-chauvinism. I
  25282. remind you that FORTRAN has always had pointers. Unfortunately, in the
  25283. beginning (and I was there), it had nothing else. Everything was passed by
  25284. pointer! C pointers have nothing to do with the issue. The issue is: FORTRAN
  25285. intrinsic mathematical functions are more complete and more robust.
  25286. Historically, scientific programmers began on mainframes without C compilers,
  25287. and of necessity, have pushed the art of numerical programming further.
  25288.  
  25289. I appreciate your efforts to bring C mathematical routines up to snuff. But
  25290. your quest for "acceptance of C by the FORTRAN old guard"? I know the old
  25291. guard. When C has enough intrinsic functions with the flexibility and
  25292. stability of the FORTRAN functions, you could feed it to him, but only if you
  25293. rename it FORTRAN.
  25294. Very truly yours,
  25295. Lin DeNoyer
  25296. Spectrumm Square Associates
  25297. 755 Snyder Hill
  25298. Ithaca, NY 14850
  25299. I too began programming Physics applications in FORTRAN back in 1963. I think
  25300. you speak well for a significant fraction of the Old Guard, of which I was
  25301. once a member. Those existing libraries are certainly important. So too are
  25302. the millions of lines of existing FORTRAN applications. All that code can in
  25303. principle be translated mechanically to Standard C. That should preserve its
  25304. flexibility and stability.
  25305. The price you pay sometimes is inferior performance. But that's not because
  25306. you access arguments via pointers, as you rightly point out. At least not
  25307. directly. The problem is that a C compiler can't optimize accesses via
  25308. argument pointers nearly as aggressively as can a FORTRAN compiler. You have
  25309. to watch what you do with a C pointer because you don't know where it's been.
  25310. C permits all sorts of aliasing with pointers that is forbidden in simpler
  25311. languages such as FORTRAN.
  25312. Your final remark is the most telling. Many FORTRAN programmers will not
  25313. switch to C simply out of habit. The new language is not sufficiently better,
  25314. to them at least, for an old dog to want to learn new tricks. I can sympathize
  25315. with that viewpoint. I feel the same way about C++ sometimes. -- pjp
  25316. Dear Editor:
  25317. Your magazine has always been helpful to me as a programmer, and perhaps you
  25318. or one of your readers may be able to help me locate an algorithm to send
  25319. voice through the IBM-PC's speaker.
  25320. A common way to reproduce voice is to feed the AM signal into an 8-bit A to D
  25321. converter, which is then sampled and the values stored in sequential order. To
  25322. "play back" the values are sent to a D to A converter which reproduces the
  25323. voice accurately enough to recognize a person's voice.
  25324. Unfortunately, the PC's speaker is driven by digital logic circuitry and
  25325. current is either on or off in the speaker coil. It is impossible, therefore,
  25326. to send an AM signal to the PC's speaker. However, one can send a constant
  25327. amplitude, variable width pulse train. The algorithm I need is: how does one
  25328. convert a string of 8-bit numbers representing an AM signal into a list of
  25329. number representing the variable width string of pulses the PC's speaker
  25330. needs, with minimal loss of information?
  25331. From what I have been able to gather, the AM waveform needs some high pass
  25332. filtering and then is sent through a differentiator. The output should be a
  25333. logical 0 if the differentiator's output is positive and 1 if negative. While
  25334. it is easy to see how one could build such a system in hardware, I want to do
  25335. it with software. Using the 8-bit code representing the AM information as
  25336. input, how does one get the "differentiated" output? The speech type program
  25337. found in shareware sources have not been of much help. Can someone point me in
  25338. the right direction?
  25339. Sincerely,
  25340. Theron Wierenga
  25341. P.O. Box 595
  25342. Muskegon, MI 49443
  25343. I haven't fiddled enough with digital filtering to be much help here. Anybody?
  25344. -- pjp
  25345. See "Writing for the PC Speaker" by Robert Bybee in Windows/DOS Developer's
  25346. Journal, December, 1991. Call our Customer Relations department for back
  25347. issues and reprints. -- dt
  25348. Ladies and Gentlemen:
  25349. I wanted to comment on a couple of things I have noted in the past few months
  25350. in letters to the magazine. First, I would really hate to see your source code
  25351. distribution system handled through CompuServe. Rather than bore you with gory
  25352. details, I'll just say that I once subscribed to CompuServe. While I agree
  25353. that the current system has its drawbacks, I believe it is infinitely
  25354. preferable to CompuServe.
  25355. Concerning the letters from those seeking the path to enlightenment (i.e.,
  25356. learning C): the best thing I ever did was read a good book on compiler
  25357. construction. I first read K&R, and achieved a certain amount of literacy in
  25358. the language (I wrote programs that worked), but many times I wasn't sure just
  25359. why things worked. In C, a language on intimate terms with its supporting
  25360. hardware, such ignorance is cumbersome at best and dangerous at worst.
  25361. However, I bought a used copy of Aho & Weinberger, and suddenly the
  25362. "incantations" took on new clarity. My approach to pointers had always been
  25363. just above the level of mysticism, but after learning how a compiler would
  25364. generate code to declare a pointer, the mystery was gone. Studying compiler
  25365. construction might seem like a big bite for a beginner, but I think that after
  25366. one has achieved technical facility with the language, it's not too large a
  25367. step. It helped me a lot more than any "teach yourself C" book I ever saw.
  25368. Lastly, I wish to toss in my opinion regarding the C/C++ controversy, i.e.,
  25369. whether you should lean more toward C++. I have a C++ compiler (Borland C++),
  25370. I have read a number of books and articles on it, and I have had the
  25371. opportunity to discuss C++ at length with a friend who recently took a course
  25372. in it at the University of Washington. While I can envision reasons why
  25373. someone would wish to use C++, those reasons do not apply to me -- nor, I
  25374. believe, to a large number of programmers and designers. C++ seems to be more
  25375. an idiom than anything else (that from my friend), and as has been stated in
  25376. articles in CUJ, it is not the only way in which to obtain source code
  25377. reusability. Further, I for one am not convinced that "object oriented
  25378. programming" is the be-all and end-all which many of its proponents seem to
  25379. claim. To my (possibly ignorant) way of thinking, C++ is a new flavor, not a
  25380. new food.
  25381. Having said all that, I would like to see you remain a magazine devoted to C
  25382. programming. If you do ever decide to become The C++ Users Journal, I will
  25383. probably find another magazine which addresses my favorite language as well as
  25384. you do now. I like C. It works for me.
  25385. Finally, regarding that guy who responded to my earlier letter in which I
  25386. requested a nude centerfold: I guess some people have nothing better to do
  25387. than to pick at nits. Can we code him a sense of humor in C, or would it take
  25388. C++?
  25389. Very truly yours,
  25390. Ian S. King 520
  25391. SW Yamhill Street #430
  25392. Portland, OR 97204
  25393. I too learn languages best by seeing how they are implemented. I'm not sure
  25394. that approach works for everybody, but if it works for you too, go for it. As
  25395. for C++, it seems that sanity is beginning to prevail. More and more people
  25396. are finding good uses for C++ and other OO languages, but the religious zeal
  25397. of the newly converted is dying out.
  25398. I can think of several clever remarks about nude centerfolds, the need for a
  25399. sense of humor, and the relative merits of C vs. C++ in this context. Robert
  25400. will probably be happier if I print none of them here. -- pjp
  25401. Maybe so, but I'm glad I got to read King's remark -- rlw
  25402. Dear Mr. Plauger,
  25403. I have a question that I was hoping you might be able to help with. I'm using
  25404. VAX C with the curses library running on a VAX3100, I've created a selection
  25405. menu, using curses, to execute other programs.
  25406. The problem I'm having is, the getch function, available with the curses
  25407. library, doesn't send the character you enter until you've also hit return. On
  25408. an IBM PC, getch will get a single character from the keyboard, and send it to
  25409. your program without having to hit the return key.
  25410. Is there another way of doing this, on the VAX using C, without having to call
  25411. other language routines? There is a function available in VAX Basic called
  25412. INKEY which looks like "INKEY$ (0%, WAIT)". There is also SMG$ routines
  25413. available on the VAX to get unsolicited keyboard input, but if I user either,
  25414. porting to other systems will be a pain.
  25415. Sincerely,
  25416. James Cook
  25417. 13213-66B Ave.
  25418. Surrey, B.C.
  25419. V3W-8P4
  25420. The issue you face is common to many systems, not just VMS. (If you haven't
  25421. hit it on the PC, you've just been lucky -- after a fashion.) The problem is
  25422. that most of the time you want keyboard input to be "cooked" a line at a time.
  25423. You want the typist to backspace over typos, even retype the whole line, until
  25424. it is correct. That means the input cooker must wait until the typist strikes
  25425. the return key before the program can see the first character on a line.
  25426. Nearly every system I've ever encountered has some way to request "raw" input.
  25427. The program gets each character as it's typed, with no line editing performed.
  25428. Sounds like your PC getch gives raw input whether you ask or not, for good or
  25429. for ill. The problem is, no standard way exists to specify raw input in C.
  25430. Your best bet is to write your code in terms of a function with a name such as
  25431. getraw. You'll have to tailor the function for each system, but at least it
  25432. hides the irregularity. -- pjp
  25433. Dear Mr. Plauger:
  25434. I enjoyed reading Mr. Rothkin's article entitled "PC UART Device Driver" in
  25435. the December 1991 issue of The C Users Journal. I have been programming the
  25436. serial port for over eight years and have developed my own routines which will
  25437. be modified based on information from this article. One thing he mentioned in
  25438. his article that he says would be difficult to handle would be the proper
  25439. handling of IRQs nine through fifteen. One method I have used is to have two
  25440. entry points to the same routine, one for one interrupt vector, and a second
  25441. for the other interrupt vector. You simply indicate which entry point was used
  25442. and proceed from there. Then, when the interrupt routine has completed, it can
  25443. check to see which entry point was used and respond to the appropriate 8259
  25444. interrupt controller. Of course there are other ways to handle the situation,
  25445. but this has been the way I have chosen for my device drivers.
  25446. Thank you for this and other articles and keep up the good work.
  25447. Very Truly Yours,
  25448. Peter R. Vermilye
  25449. The Programmers' Guild, Inc.
  25450. 1833 New Riverdale Rd.
  25451. Germantown, TN 38138
  25452. Thanks. -- pjp
  25453. Dear Sirs;
  25454. I need your help! I use Borland C++ for a compiler, Btrieve or Paradox engine
  25455. for a RDBMS, but I don't have any interface tools. I am searching for a C
  25456. and/or C++ user interface package. I would like something that is
  25457. comprehensive, yet easy to use. I want it to have a WYSIWYG designer for
  25458. menus/screens/forms and to generate program stubs (option).
  25459. I have demos of Greenleafs DataWindows, Island System's graphicsMenu,
  25460. Softway's Hi-Screen Pro II, and Vermont Creative Software's Vermont Views with
  25461. designer. These packages represent the price range I am working with. Running
  25462. the demos only confuses me more; I like features in each.
  25463. Do you have any recommendations for a C programmer looking to give his
  25464. programs a quality interface? I will appreciate any advice you can give me.
  25465. Thank you.
  25466. Sincerely,
  25467.  
  25468. Jeffrey S. Dreke
  25469. 15709 North Lund Road
  25470. Eden Prairie, MN 55346-1550
  25471. Sorry, that's another area (of many) where I'm weak. Anybody? -- pjp
  25472.  
  25473.  
  25474.  
  25475.  
  25476.  
  25477.  
  25478.  
  25479.  
  25480.  
  25481.  
  25482.  
  25483.  
  25484.  
  25485.  
  25486.  
  25487.  
  25488.  
  25489.  
  25490.  
  25491.  
  25492.  
  25493.  
  25494.  
  25495.  
  25496.  
  25497.  
  25498.  
  25499.  
  25500.  
  25501.  
  25502.  
  25503.  
  25504.  
  25505.  
  25506.  
  25507.  
  25508.  
  25509.  
  25510.  
  25511.  
  25512.  
  25513.  
  25514.  
  25515.  
  25516.  
  25517.  
  25518.  
  25519.  
  25520.  
  25521.  
  25522.  
  25523.  
  25524.  
  25525.  
  25526.  
  25527.  
  25528.  
  25529.  
  25530.  
  25531. Porting Command Line User Interfaces to GUIs
  25532.  
  25533.  
  25534. William Smith
  25535.  
  25536.  
  25537. William Smith is the engineering manager at Montana Software, a software
  25538. development company specializing in custom applications for MS-DOS and
  25539. Windows. You may contact him by mail at P.O Box 663, Bozeman, MT 59771-0663.
  25540.  
  25541.  
  25542. Not very long ago the computer industry's direction concerning operating
  25543. systems and user interfaces was uncertain and confusing. You had to be
  25544. psychic, lucky, or just plain good at reading the writing on the wall to
  25545. assess the direction the industry was going. To name just a few of the
  25546. possibilities, there was UNIX and X-Window, OS/2 and Presentation Manager,
  25547. MS-DOS and a primitive Windows 2.0, and MS-DOS and countless lesser known
  25548. third party user interface libraries. Right at the height of this confusion, I
  25549. was faced with choosing an operating system and user interface for a software
  25550. development project. I was developing a data acquisition and data management
  25551. system for an automated athletic weight training system. The customer also
  25552. wanted the program to have a modern graphical user interface (GUI).
  25553. For economic reasons and availability of development tools, I decided to write
  25554. the software in C for a target 80386 personal computer running MS-DOS. The
  25555. user interface decision was much harder. I, like many people when faced with a
  25556. tough decision, decided not to decide. I made an engineering decision to do
  25557. the user interface last. This allowed me to postpone the commitment to a
  25558. specific user interface library. This was contrary to many opinions about
  25559. designing software at the time. Instead of designing the software from the
  25560. user interface and screen level first, I designed the software by identifying
  25561. core functionality and operations independent of the user interface. To get
  25562. the project going, I specified that the software was to be first developed
  25563. using a simple command line interface (CLI). Later, when a graphical user
  25564. interface library was chosen, I would port the program to work with the GUI
  25565. library. As soon as a clear direction in the software industry emerged, I
  25566. would decide upon what GUI to use. Much to my delight and the satisfaction of
  25567. the customer, this approach eventually paid off.
  25568. Well as most of you know, Windows 3.0 came out and quickly became a success.
  25569. It became obvious what user interface to use. Eventually the project was over
  25570. and the customer ended up with both a command line version and after a
  25571. successful port, a Windows version of the software. Along the way I learned a
  25572. lot about the approach involved in making this port easy. I am going to share
  25573. with you some of what I learned about porting command line interfaces to GUI's
  25574. in general and Windows in particular.
  25575. The concept of porting I am going to discuss requires work. It is not a simple
  25576. recompile under a different environment. Granted, there are some features
  25577. built into Microsoft QuickC for Windows and version 3.0 of Borland C++ that
  25578. allow you to recompile your code under Windows with no changes. The approach
  25579. is easy, but all it gets you is a command line within a window. I do not
  25580. consider this a true GUI. To get true GUI behavior in your program you are
  25581. going to have to write some code and make some changes. GUIs are here to stay
  25582. and will become increasingly popular in the future. The effort to port your
  25583. code to a GUI is worth it. With proper planning, it does not have to be that
  25584. painful either.
  25585.  
  25586.  
  25587. CLI Versus GUI
  25588.  
  25589.  
  25590. With the popularity of GUIs, CLIs may seem antiquated, but they have their
  25591. place. They are easy to write and if you stick to the standard C library, very
  25592. portable. You can generate a test program for exercising new code quicker
  25593. using a CLI then a GUI. CLIs are fast and can be easy to use. All that the
  25594. program requires of the user is to type in the program name and some options
  25595. at the operating system prompt. The command line interface is elegant in its
  25596. simplicity and appreciated by the skilled software user. CLIs are also very
  25597. adaptable to batch mode processing. Unfortunately, CLIs are cryptic and
  25598. require the user to have knowledge and memory of the command line syntax. By
  25599. providing a help screen defining proper usage, you can relieve this challenge
  25600. somewhat for the inexperienced user. An easy to use CLI program should provide
  25601. a provision to invoke this help screen when an h or ? option is passed on the
  25602. command line. The program should also display the help information when there
  25603. is an error in the command line syntax.
  25604. GUIs are visually appealing and easier to negotiate then CLIs especially for
  25605. the unskilled user. GUIs can require more steps to accomplish the same task
  25606. then a CLI and are not as conducive to batch mode processing as CLIs. With
  25607. some effort, you can add key stroke short cuts and batch ability to GUIs to
  25608. satisfy the demands of the advanced user.
  25609. Table 1 contains a list of user interface characteristics and features. It
  25610. rates CLIs and GUIs for comparison.
  25611.  
  25612.  
  25613. CLI Translated to GUI
  25614.  
  25615.  
  25616. Programs in the simplest terms require input, perform some task and generate
  25617. output. The input information is in the form of instructions and data. The
  25618. output information consists of results and data. With a command line interface
  25619. your choices of how to communicate instructions to a program are limited.
  25620. Typically, CLIs use single characters, sometimes preceded by a delimiting
  25621. character such as /, to specify options, commands, or flags. The user passes
  25622. data to CLIs in the form of file names or lists of strings. On the other hand
  25623. there are many more options available on how to communicate information to a
  25624. program that employs a GUI. Table 2, lists the basic command line interface
  25625. elements and corresponding GUI elements. The nomenclature is based on Windows.
  25626. Notice there is not a one-to-one correspondence between a command line element
  25627. and a GUI element. With a GUI, there can be many different ways to accomplish
  25628. the same task. This gives the developer some flexibility in designing the
  25629. interface.
  25630. Listing 1 contains a code fragment from the main function of a program that
  25631. processes a command line and performs database operations. This is an excerpt
  25632. from a data base utility program that I first created as a CLI program and
  25633. later ported to Windows. The command line is simple. To specify an operation,
  25634. the program requires a single character as the first argument on the command
  25635. line. The second and third arguments are a file name and a key name. The
  25636. operation chosen determines which of these last two arguments are required.
  25637. The command line syntax is
  25638. PROGRAM OPERATION FILE KEY
  25639. The possible operations are shown in Table 3.
  25640. Listing 2 contains an excerpt from a Windows program. This code is taken from
  25641. a program that accomplishes the same tasks as the program that contains
  25642. Listing 1 . The code is from the windows procedure for the main window that
  25643. responds to messages for the main window. The code fragment contains a switch
  25644. statement that reacts to menu choices that are in the form of messages
  25645. communicated from Windows. There is correspondence between the command line
  25646. options and the menu choices. The user specifies the file and key through
  25647. interaction with dialog boxes. Notice that the calls to the functions,
  25648. add_data_to_db, del_data_from_db, get_data_from_db, replace_data_in_db,
  25649. vrfy_data_in_db are the same for both the CLI version and the GUI version. The
  25650. code for these function should be portable across user interfaces.
  25651. Table 4 lists the CLI arguments and the corresponding GUI elements used to
  25652. accomplish the same operations.
  25653.  
  25654.  
  25655. GUI Portability Strategy
  25656.  
  25657.  
  25658. There are three major guidelines that form the foundation of a strategy for
  25659. portability between user interfaces:
  25660. 1. Identify high level functionality and data structures
  25661. 2. Isolate program functionality from user interface code
  25662. 3. Use standard library and standard types
  25663. Planning for user interface portability requires adopting a design philosophy
  25664. of first identifying high level program functionality and avoiding the
  25665. specifics of screen design. The idea is to isolate the major data structures
  25666. and operations that the program supports. If you can wrap a command line
  25667. interface around the operations that your program performs, you are on the
  25668. right track. Granted some programs such as word processors do not lend
  25669. themselves to command line interfaces. Even in this situation, you can isolate
  25670. individual functions a word processor performs and group them in a utility
  25671. program with a command line interface.
  25672. Once you define the major functionality, make sure you strongly segregate the
  25673. code you write to implement this functionality from the user interface code.
  25674. The modules that contain the core operations of your program should be
  25675. portable and not make any function calls to a user interface library.
  25676. To help with portability, use the standard library functions and the standard
  25677. types. Some of the issues encountered when porting among GUIs and operating
  25678. systems are sizes of standard types, structure packing, and alignment. The
  25679. size of some of the standard types will change from one platform to another.
  25680. An example is the default int type. Under some compilers an int is 16 bits
  25681. while with others it is 32 bits. If you do not care what size it is and want
  25682. to use the native most efficient size, use just a plain int. If you only need
  25683. 16 bits and want to conserve space in a platform where int is 32 bits use
  25684. short int. If you need 32 bits even in a platform where int is 16 bits use
  25685. long int. Avoid typedefing int and encoding the size in the type such as int16
  25686. or int32. Some claim that this increases portability, but I have found the
  25687. exact opposite to be true. I also recommend using the size_t type defined in
  25688. standard C. size_t is defined as an unsigned integer. It is convenient to use
  25689. variables of type size_t as array indexes.
  25690. Related to type size is structure packing and alignment. In most situations,
  25691. compilers align structure members (except chars) on boundaries that correspond
  25692. to the most efficient type size. On a 16-bit system, structure members are
  25693. aligned on word boundaries. On a 32-bit system, structure members are aligned
  25694. on double word boundaries. Some compilers allow the program to control
  25695. structure alignment.
  25696. Try to avoid dependencies in your code on type size and structure alignment.
  25697. The sizeof operator can help with types and the offsetof macro can help with
  25698. structures. Pay careful attention to third party libraries if you use them.
  25699. They may have size and structure alignment dependencies that could bite you
  25700. later.
  25701. Buffer sizes and string lengths should be set using manifest constants. For
  25702. example, the maximum length of a string to hold a file name may change from
  25703. one system to another. It is far easier to change the definition of a manifest
  25704. constant in one place than find all places where space for a string is
  25705. allocated.
  25706.  
  25707.  
  25708. Porting to Windows -- the Gruesome Details
  25709.  
  25710.  
  25711. Since Windows is such a popular GUI, it is worth talking about some of the
  25712. specific issues encountered when porting existing C code to this environment.
  25713. As a first step in porting your CLI program to the Windows GUI, you may want
  25714. to create a user interface shell and spawn the CLI version of your program
  25715. using the Windows WinExec function call. WinExec is similar to the spawn
  25716. function family in standard C. This approach will get you up and running, but
  25717. I found it unacceptable for a finished product. The major drawback is the lack
  25718. of and the difficulty involved in communicating between MS-DOS and Windows
  25719. programs.
  25720. The next step is to replace the CLI interface and compile your code as a
  25721. Windows application. Unfortunately, even if you prepared ahead for portability
  25722. there are some problems that may surface.
  25723.  
  25724.  
  25725.  
  25726. Types and Structure Alignment
  25727.  
  25728.  
  25729. I have already mentioned data types and structure packing. They are especially
  25730. important issues under Windows. Windows, in its present incarnation, is a
  25731. 16-bit environment and the default int type is 16 bits, but Windows requires
  25732. structures to be aligned on eight-bit (byte) boundaries. If your code expects
  25733. structures to be aligned on 16-bit (word) boundaries this may affect you. I
  25734. ran into alignment problems with a third party database library. The situation
  25735. forced me into hand padding my structures so members greater than a single
  25736. byte in size were aligned on word boundaries. I inserted eight-bit padding
  25737. members of type char after an odd number of single byte members.
  25738. The Windows programing environment contains many new types defined in the
  25739. include file, WINDOWS.H. I recommend you use these types, but confine their
  25740. usage to the user interface portions of your code.
  25741.  
  25742.  
  25743. Memory Models
  25744.  
  25745.  
  25746. Since Windows runs under MS-DOS and is subject to the caveats of the Intel
  25747. segmented architecture, you will have to deal with near and far pointer issues
  25748. and memory models. Since I wanted as much of my code to be as standard C-like
  25749. as possible, I tried to avoid sprinkling my code with the keywords near and
  25750. far that are not standard C keywords. The general wisdom on Windows claims
  25751. that programs compiled using the small or medium memory model behave better
  25752. under Windows than those compiled with the large or compact memory models.
  25753. Using the small or medium memory model forces you to declare pointers with the
  25754. far keyword if they happen to be in far heap space. Even though using the
  25755. large and compact memory models is discouraged, many of the Windows library
  25756. functions also require far pointers as parameters. The only way to get far
  25757. pointers without adding the far keyword to every declaration is to use the
  25758. large or compact memory model. Windows does not like programs compiled under
  25759. these memory models because they may contain multiple data segments. Windows
  25760. fixes multiple data segments in memory. This situation prevents Windows from
  25761. running more than one instance of such programs and may cause inefficiencies
  25762. in Windows memory management. This is the case with Microsoft C, but not
  25763. always with Borland C++. Yes, that is right, the two compilers have a slightly
  25764. different implementation of the large and compact memory models. Microsoft C
  25765. creates multiple data segments when using the large or compact memory model
  25766. and you have little control over the outcome. Borland C++ creates a single
  25767. data segment unless you specifically tell it to create more. You can run
  25768. multiple instances of a program compiled under Borland C++ using the large or
  25769. compact memory model. The exact program compiled with Microsoft C using the
  25770. large memory model will run as a single instance only.
  25771. I have tried the large memory model under Windows and did not notice any
  25772. performance problems with Windows in standard or enhanced mode. I never even
  25773. tried real mode. Since Windows 3.1 eliminates real mode, I probably never will
  25774. use real mode. If you need pointers to far data, your choices are to use the
  25775. large memory model or to use mixed model programming by declaring pointers
  25776. with the far keyword.
  25777.  
  25778.  
  25779. Dynamic Memory
  25780.  
  25781.  
  25782. Windows does support the malloc family of standard C library memory management
  25783. functions. Unfortunately, they may have slightly different behavior then what
  25784. you are use to. Depending on the memory model, malloc may allocate memory in
  25785. the near heap. If you want to force allocation in the far heap independent of
  25786. memory model, you will have to use the function _fmalloc. Since Windows maps
  25787. _fmalloc to the Windows function GlobalAlloc, there is a limitation on how
  25788. many times you can call _fmalloc. Every time you call GlobalAlloc, Windows
  25789. uses a segment selector. There is a finite number of segment selectors
  25790. available. This happens to be 8192 for all of Windows - not just your
  25791. application. If your program requires the allocation of a lot of small pieces
  25792. of memory, you can quickly run out of selectors even if you still have lots of
  25793. free memory. The solution to this problem is to call GlobalAlloc sparingly and
  25794. use subsegment allocation. This means you will have to write your own memory
  25795. manager or buy one of the third party libraries on the market. Version 3.0 of
  25796. Borland C++ supports subsegment memory allocation and eliminates this problem.
  25797. I expect eventually all compilers that support Windows will support this
  25798. feature.
  25799.  
  25800.  
  25801. WINSTUB.EXE
  25802.  
  25803.  
  25804. Windows allows a non-Windows program to be bound to your Windows program. You
  25805. specify the stub program in the linker definition file. MS-DOS executes the
  25806. stub program when you invoke the program from the MS-DOS prompt. I took
  25807. advantage of this feature to bind the command line or MS-DOS version of a
  25808. program to the GUI or Windows version of the same program. The only problem I
  25809. encountered with this was that Borland C++ enforced a 64KB maximum size
  25810. limitation on the stub program. Microsoft C allowed the stub program to be any
  25811. size.
  25812.  
  25813.  
  25814. UAEs and New Bugs under Windows
  25815.  
  25816.  
  25817. When I first compiled my program under Windows as a Windows application, I was
  25818. extremely disappointed when it would not run without generating UAEs
  25819. (Unrecoverable Application Errors). Upon tracking down the offending lines of
  25820. code, a pattern started to emerge. The majority of the UAEs where caused by
  25821. dereferencing null pointers. This was occurring in the code I had written and
  25822. also in the standard library code that I was passing null pointers to as
  25823. parameters. Since the program worked fine under MS-DOS, there was some
  25824. argument among co-workers about whether these were actual bugs. One of my
  25825. partners claimed that functions such as strcmp should be able to handle a null
  25826. pointer parameter. Since I could not find any reference that specified how
  25827. some of the standard library functions responded to null pointers as
  25828. parameters, I decided to be conservative on this issue and actually modified
  25829. the program's code to avoid passing null pointers to functions where the
  25830. behavior was not defined and caused UAEs. I recommend that you be careful
  25831. about dereferencing null pointers and passing null pointers to standard
  25832. library functions where the behavior is not specifically defined by the
  25833. standard or defined in the function description that comes with your
  25834. compiler's documentation.
  25835.  
  25836.  
  25837. Conclusions
  25838.  
  25839.  
  25840. Ease in portability between user interfaces requires planning. A decision to
  25841. first develop an application with a command line interface and then port it to
  25842. Windows made me deal head on with GUI portability problems. What I eventually
  25843. ended up with is a program where most of the code will port to any GUI without
  25844. rewriting it.
  25845. Planning for portability requires you to identify the needed operations and
  25846. the high level data elements independent of any user interface issues. You
  25847. should isolate user interface specific code from the core program code. You
  25848. should be able to access the functionality of your program through a simple
  25849. command line interface. This can be handy for testing. For a CLI, the main
  25850. function should do nothing but process the command line and make the requested
  25851. function calls. These same function calls can then be called in a similar way
  25852. when responding to messages or events in a program with a GUI. The GUI program
  25853. will have to be more than just a simple main function module and may require
  25854. many new modules that support the GUI functionality and screens. The goal is
  25855. to have the business end of the code that does the data crunching and
  25856. calculating remain unchanged when porting from one user interface to another.
  25857. Table 1 User Interface Issues, CLI Versus GUI
  25858. Characteristics/Features CLI GUI
  25859. ---------------------------------------------
  25860. Ease of Development Good Poor
  25861. Portability Good Poor
  25862. Batch Processing Good Poor
  25863. Ease of Use Poor Good
  25864. Aesthetics and Presentation Poor Good
  25865. Visibility of Information Poor Good
  25866. Flexibility of Program Operation Poor Good
  25867. Number of Interaction Steps Few Many
  25868. Table 2 CLI Elements and Analogous GUI Elements
  25869. CLI Element Corresponding GUI Elements
  25870. ----------------------------------------------------
  25871. Option/Action Flag Menu Item, Radio Button,
  25872.  Check Box
  25873. String Unlimited Choices Edit Box, Check Box
  25874. String Limited Choices Menu Item, Radio Button,
  25875.  List Box
  25876. File Name Edit Box, List Box,
  25877.  Dialog Box
  25878. Script File Editor, Dialog Box,
  25879.  
  25880.  Custom Builder
  25881. Table 3
  25882. Operation Description
  25883. ---------------------------------------------------------------
  25884. A add data contained in FILE to database
  25885. D delete data specified by KEY from database
  25886. G get data specified by KEY from database and
  25887.  store in FILE
  25888. L list all the keys in the database to standard output
  25889. R replace data in database specified by KEY
  25890.  with data in FILE
  25891. V verify data in database specified by KEY with
  25892.  data in FILE
  25893. Table 4 CLI Arguments and Corresponding GUI Elements
  25894. CLI Arguments GUI Elements
  25895. ---------------------------------------
  25896. Option Options (Menu)
  25897.  A Add...
  25898.  D Delete...
  25899.  G Get...
  25900.  L
  25901.  R Replace...
  25902.  V Verify...
  25903. File name File Select Dialog Box
  25904. Key name Key Select Dialog Box
  25905.  
  25906. Listing 1
  25907. switch ( argv[1][0] )
  25908. {
  25909. case 'a':
  25910. case 'A':
  25911. status = add_data_to_db( argv[2] );
  25912. break;
  25913. case 'd':
  25914. case 'D':
  25915. status = del_data_from_db( argv[2] );
  25916. break;
  25917. case 'g':
  25918. case 'G':
  25919. status = get_data_from_db( argv[2], argv[3] );
  25920. break;
  25921. case 'l';
  25922. case 'L';
  25923. status = list_keys_in_db();
  25924. break;
  25925. case 'r';
  25926. case 'R';
  25927. status = replace_data_in_db( argv[2], argv[3] );
  25928. break;
  25929. case 'v':
  25930. case 'V':
  25931. status = vrfy_data_in_db( argv[2], argv[3] );
  25932. break;
  25933. default:
  25934. status = FAIL;
  25935. break;
  25936. } /*switch ( argv[1][0] )*/
  25937.  
  25938. /* End of File */
  25939.  
  25940.  
  25941.  
  25942. Listing 2
  25943. switch ( wParam )
  25944. {
  25945. case IDM_ADD:
  25946. /* Select a File */
  25947. status = FileSelectDialog( hInstance, hWnd,
  25948. Caption, FileSpec );
  25949. if ( status == FAIL status == FALSE )
  25950. break; /* Canceled the operation. */
  25951. /* Add file to database */
  25952. status = add_data_to_db ( FileSpec );
  25953. break:
  25954. case IDM_DELETE:
  25955. /* Select a Key */
  25956. status = KeySelectDialog ( hInstance, hWnd,
  25957. Caption, Key );
  25958. if ( status == FAIL status == FALSE )
  25959. break; /* Canceled the operation. */
  25960. /* Delete Keyed Data from database */
  25961. status = del_data_from_db( Key );
  25962. break;
  25963. case IDM_GET:
  25964. /* Select a Key */
  25965. KeySelectDialog( hInstance, hWnd,
  25966. Caption, Key );
  25967. if ( status == FAIL status == FALSE )
  25968. break; /* Canceled the operation. */
  25969. /* Select a File */
  25970. FileSelectDialog( hInstance, hWnd,
  25971. Caption, FileSpec );
  25972. if ( status == FAIL status == FALSE)
  25973. break; /* Canceled the operation. */
  25974. /* Get data from database and store in File */
  25975. status = get_data_from_db( Key, FileSpec );
  25976. break;
  25977. case IDM_REPLACE:
  25978. /* Select a Key */
  25979. KeySelectDialog ( hInstance, hWnd,
  25980. Caption, Key );
  25981. if ( status == FAIL status == FALSE )
  25982. break; /* Canceled the operation. */
  25983. /* Select a data file */
  25984. FileSelectDialog( hInstance, hWnd,
  25985. Caption, FileSpec );
  25986. if ( status == FAIL status == FALSE )
  25987. break; /* Canceled the operation. */
  25988. /* Replace Key data with data in FileSpec */
  25989. status= replace_data_in_db ( Key, FileSpec );
  25990. break;
  25991. case IDM_VERIFY:
  25992. /* Select a Key */
  25993. KeySelectDialog( hInstance, hWnd,
  25994. Caption, Key );
  25995. if ( status == FAIL status == FALSE )
  25996. break; /* Canceled the operation. */
  25997. /* Select a File */
  25998. FileSelectDialog( hInstance, hWnd,
  25999.  
  26000. Caption, FileSpec );
  26001. if ( status == FAIL status == FALSE )
  26002. break; /* Canceled the operation. */
  26003. /* Verify Data in database (Key) matches
  26004. ** data in FileSpec */
  26005. status = vrfy_data_in_db( Key, FileSpec );
  26006. break:
  26007. default:
  26008. status = FAIL;
  26009. break;
  26010. } /* switch (wParam) */
  26011.  
  26012. /* End of File */
  26013.  
  26014.  
  26015.  
  26016.  
  26017.  
  26018.  
  26019.  
  26020.  
  26021.  
  26022.  
  26023.  
  26024.  
  26025.  
  26026.  
  26027.  
  26028.  
  26029.  
  26030.  
  26031.  
  26032.  
  26033.  
  26034.  
  26035.  
  26036.  
  26037.  
  26038.  
  26039.  
  26040.  
  26041.  
  26042.  
  26043.  
  26044.  
  26045.  
  26046.  
  26047.  
  26048.  
  26049.  
  26050.  
  26051.  
  26052.  
  26053.  
  26054.  
  26055.  
  26056.  
  26057.  
  26058.  
  26059.  
  26060.  
  26061.  
  26062.  
  26063. A Versatile Menu Program for Turbo C
  26064.  
  26065.  
  26066. Roger T. Stevens
  26067.  
  26068.  
  26069. Dr. Roger T. Stevens is a member of the technical staff of the MITRE
  26070. Corporation, Bedford, MA. He holds a B. A. degree in English from Union
  26071. College, an M A in Mathematics from Boston University, an M. Eng. in Systems
  26072. Engineering from Virginia Tech, and a PhD. in Electrical Engineering from
  26073. California Western University. Dr. Stevens' books Graphics Programming in C,
  26074. Fractal Programming in C, and Fractal Programming in Turbo Pascal are
  26075. published by M & T Publishing, Inc., 501 Galveston Dr., Redwood City, CA
  26076. 94063.
  26077.  
  26078.  
  26079.  
  26080.  
  26081. Introduction
  26082.  
  26083.  
  26084. Although there are menu programs available commercially and as shareware, most
  26085. of them provide only the capability to display and select from a list of menu
  26086. items, and do not permit interaction with the display. However, interaction
  26087. with the display is often essential for efficient operation of a program. Look
  26088. at the sample display of Figure 1. The two bottom lines are provided by the
  26089. menu function. They give the user the choice of the actions: DISPLAY DATA,
  26090. CHANGE DATA, ADD DATA, or QUIT. All you can do with most menu functions is to
  26091. select one these actions. However, for the first two of these actions, we want
  26092. to interact with the display, by selecting a name from the list for the action
  26093. to be performed upon. The menu function described below will automatically
  26094. switch mode after an action is selected. For the first two actions, the new
  26095. mode can permit scanning the list of names with the up and down cursor arrows,
  26096. highlighting each in turn with a contrasting color combination. The user then
  26097. hits ESC to activate the selected function. The cursor is only permitted to go
  26098. to the first letter of each name in the list.
  26099. Now look at the display of Figure 2. This is a new user generated display
  26100. which provides detailed data on the person selected from the first display.
  26101. Again the menu function is activated; this time it just displays two lines of
  26102. instructions at the bottom of the screen. This is the display that you see if
  26103. you chose the action CHANGE DATA, from the first display. Those places on the
  26104. screen where you are allowed to change the data are actually shown on this
  26105. display in a different color from the rest of the display and the cursor is
  26106. restricted to only these positions. After you have modified the data in any
  26107. way you desire, hitting ESC returns to the main program. The user can then
  26108. arrange to read any data changes from the screen into his data files. Note
  26109. that as long as you are within the menu function, you have the capability to
  26110. move the cursor to any permissible location and change or rechange the data
  26111. there. If at the first screen, you selected the DISPLAY DATA action, the
  26112. bottom explanatory lines simply say Hit any key to continue... and no
  26113. modification of display data is permitted.
  26114. For the ADD DATA action, no selection from the list of names is permitted;
  26115. instead the display immediately switches to that of Figure 2, but with the
  26116. data areas blank, ready to receive data on a new person.
  26117. All of the menu and display interaction is controlled by a versatile menu
  26118. function which provides a number of different modes of operation through
  26119. passed parameters. This menu function is described in the text that follows,
  26120. and the code is in Listing 1. The listing also includes a number of useful
  26121. functions needed for program operation.
  26122.  
  26123.  
  26124. What You Can Do with the Menu Program
  26125.  
  26126.  
  26127. The menu program begins by displaying two adjacent lines of instructions or
  26128. menu items on the screen. You can select the location of the first of these
  26129. lines by setting a parameter called first_line_loc, which is passed to the
  26130. menu function. The first line normally consists of general instructions; the
  26131. second line, which is the next line after the first line, can be a list of
  26132. menu items from which the user makes a selection, or can be another set of
  26133. general instructions, depending upon the mode of operation of the menu
  26134. program.
  26135. You can specify the color combination for the menu items and the color
  26136. combination which is used to highlight the currently selected menu item. The
  26137. rest of the screen display remains as you generated it before calling the menu
  26138. function. A menu item is selected by use of the cursor arrows; when selection
  26139. is complete, this phase of the menu program is terminated by hitting the Enter
  26140. key. For any number of menu items, beginning with the leftmost, you can
  26141. specify an alternate (screen) mode of operation, which is entered after the
  26142. Enter key is hit. You can specify each permissible cursor position for this
  26143. alternate mode, and the cursor will only be allowed to go to these selected
  26144. positions. (For example, if the down arrow is hit, the menu function will look
  26145. at the current column and the next line and move the cursor there if it is a
  26146. permissible position. If that position is not permissible, the function will
  26147. look for the nearest permissible position on that line; if there is none, it
  26148. will look at the next line, and so forth until a permissible location is
  26149. found.) You can also specify two lines of text which will appear on the menu
  26150. lines when the alternate mode is entered. You can specify whether this
  26151. alternate mode of operation will be a select or an enter text type of
  26152. operation. If selection is chosen, the text from the cursor to the next
  26153. occurrence of two adjacent spaces is highlighted. Usually for this type of
  26154. operation, the only allowable cursor positions are at the beginning of each
  26155. selectable item on the display, so the entire selected item is highlighted.
  26156. If you specified the enter text type of operation, the user may enter
  26157. alphanumeric data in any permissible cursor location. You may specify the
  26158. color combination which this entered data will have. When data entry is
  26159. complete, the same escape character specified above is used to terminate the
  26160. screen mode of operation.
  26161.  
  26162.  
  26163. Determining Permissible Cursor Positions
  26164.  
  26165.  
  26166. The heart of the menu program is the capability to specify which positions on
  26167. the screen the cursor is permitted to occupy. When in the selection mode,
  26168. there should only be one permissile cursor position for each item to be
  26169. selected. That is usually the first character of the item description. When
  26170. changing or entering data on the screen, a file structure is usually
  26171. determined, which specifies the names of the data items for each file entry
  26172. and the length of each of these items. A display location and length is
  26173. established for the display of each item, and the cursor is only allowed to go
  26174. to the allocated space for each item. These areas of the screen can then be
  26175. read to obtain the modified data. By prohibiting the cursor from going to
  26176. other areas, it becomes impossible for the user to overwrite item definitions
  26177. or to enter data that is too long to fit into the file.
  26178. The permissible cursor positions are controlled in the menu program by use of
  26179. a bit map, which contains one bit for each character position on the screen.
  26180. The bit map consists of a character array of 25 by l0 characters. The 25 is
  26181. for the 25 screen lines; the 10 is for ten bytes of eight bits each to provide
  26182. for the 80 character positions on a line.
  26183. If a particular bit is one, the cursor is permitted to go to that location; if
  26184. it is a zero, the cursor is prohibited from going there. The bit map for a
  26185. particular menu may be generated dynamically or it may be generated manually
  26186. by determining what bits need to be set and where they are located in the
  26187. array. Since this latter process can be rather cumbersome, a utility function,
  26188. set_cursor, has been provided to do the job automatically. You temporarily
  26189. insert set_cursor into your program, just after the display has been
  26190. generated. You can now move the cursor around the display and insert an x or X
  26191. at every position where the cursor is to be allowed. You should also replace
  26192. any x or Xs that occur naturally in the display in locations prohibited to the
  26193. cursor with some other character. Make sure not to hit the Enter key until you
  26194. have inserted all of the required xs in the display.
  26195. When you hit Enter the program reads the entire screen and generates a file
  26196. called MATRIX.C, which contains all of the ASCII data needed for initializing
  26197. a cursor map array in your program. You can set up the bit map array by
  26198. inserting the following line in your program:
  26199. char nnnnnnnn[25][10] =
  26200. where nnnnnnnn is the name of your bit map. If you are using the Turbo C total
  26201. environment editor, place the cursor after the equals sign and type ^KR. When
  26202. asked for the file name, type in MATRIX.C. The required data for the bit map
  26203. will then be inserted into your program. You can then remove set_cursor from
  26204. your listing and recompile the program. When you run the menu function, you
  26205. will find that the cursor will only go to those positions which you marked
  26206. with an x or X when you used set_cursor to construct the bit map.
  26207.  
  26208.  
  26209. Key Designations
  26210.  
  26211.  
  26212. Normal keys on the IBM PC keyboard generate the standard ASCII representation
  26213. of the selected letter or number. Most special keys, such as the cursor arrow
  26214. keys and the F1 through F10 function keys return two characters, first a hex 0
  26215. and then some number from 1 to 255. To put all key returns into a common
  26216. format, the menu program automatically reads a second character from the
  26217. keyboard when the first character is 0 and adds 256 to this second character
  26218. to obtain a unique number that will not duplicate one of the normal ASCII
  26219. codes. This key input is stored in a variable called key_id. Thus, when
  26220. looking at the program listing, some of the comparisons of key_id with various
  26221. numbers may appear unfamiliar. They can be identified by taking any chart of
  26222. keyboard codes and adding 256 to the second character generated by a
  26223. particular key. The menu program has the flexibility of specifying which key
  26224. will be used to escape from the screen type of operation. This capability will
  26225. be described in furthur detail below. You should note, however, that whatever
  26226. character you select for escape cannot be entered as data on the screen.
  26227.  
  26228.  
  26229. Menu Options
  26230.  
  26231.  
  26232. The programmer has almost unlimited flexibility in defining how the menu
  26233. program is to be used. The menu options are selected by parameters passed
  26234. through the menu function.
  26235. The first parameter passed to the menu function is a string called values
  26236. which defines the characteristics of the menu line. It begins with two digits
  26237. which define the number of menu function items. Four digits then follow for
  26238. each menu item. The first two represent the column of the display at which
  26239. that menu item begins. The next two represent the number of characters in the
  26240. menu item. These four digit entries must correspond to the actual spacing of
  26241. the entries in the menu line string described below.
  26242. The next parameter, called menu_first_line, is a string showing the first line
  26243. of instructions when the program is showing the menu type display. Following
  26244. that is a string called menu_second_line, which is the second line of
  26245. instructions for the menu type (the actual set of menu function items). Next
  26246. are two strings called screen_first_line and screen_second_line, which are the
  26247. first and second lines of instructions when the menu program switches to the
  26248. second type of operation.
  26249. The next parameter, called menu_type, selects the mode of operation. There are
  26250. four modes of operation for the menu function, which are controlled by this
  26251. parameter. They are assigned by one of the numbers zero to three. The modes of
  26252. operation are:
  26253. 0 = When the menu program is called, it will start with the menu display. When
  26254. it is in the screen display type of operation, all alphanumerics will be
  26255. ignored.
  26256. 1 = When the menu program is called, it will start with the menu display. When
  26257. it is in the screen display type of operation, all alphanumerics will be
  26258. displayed where typed.
  26259. 2 = When the menu program is called, it will start with the screen display.
  26260. When it is in the screen display type of operation, all alphanumerics will be
  26261. ignored.
  26262. 3 = When the menu program is called, it will start with the screen display.
  26263. When it is in the screen display type of operation, all alphanumerics will be
  26264. displayed where typed.
  26265. The next parameter, screen_color, is the screen color. This has no effect upon
  26266. the display that has already been generated, but does determine what color
  26267. combination will be used for alphanumerics typed on the screen and to restore
  26268. the background for that part of the two instruction lines that is not used.
  26269. Normally it should be the same color combination used in generating the
  26270. original display screen. Table 1 shows the color combinations represented by
  26271. each number. The next parameter, called menu_color, is the color combination
  26272. used by the two lines of instructions produced by the menu. The next
  26273. parameter, called highlight_color, is the color combination used to highlight
  26274. the selected menu item.
  26275.  
  26276. The next parameter determines how many menu functions will switch to the
  26277. screen type of operation when selected. Those functions which switch to the
  26278. screen type of operation must be grouped at the beginning of the menu line,
  26279. since the count in this variable begins with the leftmost function.
  26280. The next parameter, called escape_char, is the representation of the key which
  26281. must be hit to escape from the screen type of operation. It is a number which
  26282. is the ASCII value produced by a regular key or the value plus 256 if the
  26283. keyboard output is a two character output beginning with 0. Thus any keyboard
  26284. output may be selected. When this key is hit, it will immediately cause an
  26285. exit from the screen mode of operation. The next parameter, called
  26286. first_line_loc, determines the line on which the first of the two menu lines
  26287. begins. It may be any screen line from 0 to 23. Normally the menu lines should
  26288. be either at the top or bottom of the screen.
  26289. The final parameter is the address of the bit map, which determines which are
  26290. the permissible positions of the cursor. The bit map has already been
  26291. described above.
  26292.  
  26293.  
  26294. Supporting Functions
  26295.  
  26296.  
  26297. The menu function uses several supporting functions. These include
  26298. clearscreen, which clears the screen by filling it with spaces of a designated
  26299. color; gotoxy, which positions the cursor at a desired column and row;
  26300. putcolorchar, which displays a character at the cursor location with a
  26301. specified color combination and moves the cursor to the next column;
  26302. color_printf, which acts like the standard C printf function except that it
  26303. displays its data with a selected color combinations; and change_line_color,
  26304. which changes the color of a line of characters from the current cursor
  26305. position up until a double space is encountered. Listings of these functions
  26306. are shown for completeness. Many of them may already be available in standard
  26307. libraries, but they are not included with the current version of Turbo C. If
  26308. you are adapting the menu funciton for a monochrome display, standard
  26309. monochrome equivalent functions may be used in place of some of these
  26310. functions.
  26311.  
  26312.  
  26313. Conclusions
  26314.  
  26315.  
  26316. The menu function provides a great deal of flexibility in manipulating data
  26317. and making menu selections. About the only restriction is that all menu
  26318. function item names must fit on one line. Colors, instructions, titles, order
  26319. of mode, cursor settings, and whether or not alphanumerics are to be displayed
  26320. are all under the control of the programmer through the manner in which he
  26321. calls the menu function.
  26322. Figure 1 Name List Display
  26323.  List of Names
  26324.  
  26325.  John S. Jones
  26326.  Arthur E. Smith
  26327.  William S. Thompson
  26328.  Thomas F. Doughty
  26329.  Edgar Snow
  26330.  J. Theophilus Johnson
  26331.  Peter T. Timkins
  26332.  
  26333.  
  26334. Select desired menu function with cursor arrows - then hit 'Enter'
  26335. DISPLAY DATA CHANGE DATA ADD DATA QUIT
  26336. Figure 2 Display of Detailed Individual Data
  26337.  Name: John S. Jones
  26338.  Address: 132 Main St.
  26339.  City: Rutland State: VT Zip: 01023
  26340.  
  26341.  Phone: (802) 555-6432
  26342.  
  26343.  
  26344. Change data as required:
  26345. Then hit 'Esc'.
  26346. Table 1 Color Table
  26347. BACKGROUND Black Blue Green Cyan Red Magenta Brown Light Gray
  26348. FOREGROUND
  26349. ------------------------------------------------------------------------
  26350. Bright White 15 31 47 63 79 95 111 127
  26351. Yellow 14 30 46 62 78 94 110 126
  26352. Light Magenta 13 29 45 61 77 93 109 125
  26353. Light Red 12 28 44 60 76 92 108 124
  26354. Light Cyan 11 27 43 59 75 91 107 123
  26355. Light Green 10 26 42 58 74 90 106 122
  26356. Light Blue 9 25 41 57 73 89 105 121
  26357. Dark Gray 8 24 40 56 72 88 104 120
  26358. Light Gray 7 23 39 55 71 87 103 119
  26359. Brown 6 22 38 54 70 86 102 118
  26360. Magenta 5 21 37 53 69 85 101 117
  26361. Red 4 20 36 52 68 84 100 116
  26362. Cyan 3 19 35 51 67 83 99 115
  26363. Green 2 18 34 50 66 82 98 114
  26364. Blue 1 17 33 49 65 81 97 113
  26365. Black 0 16 32 48 64 80 96 112
  26366.  
  26367.  
  26368. Listing 1
  26369. /*
  26370. menu = controls cursor movement and menu display and selection -
  26371. returns the number of the selected menu item.
  26372.  
  26373. values: a string consisting of two digits showing
  26374. the number of menu choices, followed by
  26375. four digits for each choice, the first
  26376. two showing the starting column of the
  26377. menu item and the second two its length.
  26378.  
  26379. menu_first_line: the first line of instructions in the
  26380. menu mode.
  26381.  
  26382. menu_second_line: the second line in the menu mode. It
  26383. contains the menu choices.
  26384.  
  26385. screen_first_line: the first line of instructions in the
  26386. screen mode.
  26387.  
  26388. screen_second_line: the second line of instructions in the
  26389. screen mode.
  26390.  
  26391. menu_type: 0: Start with Menu Mode.
  26392. Alphanumeric characters are ignored
  26393. when entered in screen mode.
  26394. 1: Start with Menu Mode.
  26395. Alphanumeric characters are displayed
  26396. when entered in screen mode
  26397. 2: Start with Screen Mode.
  26398. Alphanumeric characters are ignored
  26399. when entered in screen mode.
  26400. 3: Start with Screen Mode.
  26401. Alphanumeric characters are displayed
  26402. when entered in screen mode.
  26403.  
  26404. screen_color: color for screen mode.
  26405. menu_color: color for menu display.
  26406.  
  26407. highlight_color: color of selected menu item
  26408.  
  26409. select_no: number of menu items using selection
  26410.  
  26411. escape_char: number of the key selected for escape
  26412. from the screen display.
  26413.  
  26414. map: bit map of permitted cursor locations.
  26415. The cursor will go to permitted locations
  26416. only and no others.
  26417. */
  26418.  
  26419. int menu(char values[],char menu_first_line[],char menu_second_line[],
  26420. char screen_first_line[],char screen_second_line[],int menu_type,
  26421. int screen_color,int menu_color,int highlight_color, int select_no,
  26422. int escape_char, int first_line_loc, char map[25][10])
  26423. {
  26424. union REGS reg;
  26425. int i,choices,indx,start,length,menu_second_line_length;
  26426.  
  26427. int interim,remainder,temp;
  26428. char spaces[80],prev_char;
  26429.  
  26430. for (i=0; i<76; i++)
  26431. spaces[i] = ' ';
  26432. spaces[76] = '\0';
  26433. menu_second_line_length = strlen(menu_second_line);
  26434. gotoxy(2,first_line_loc);
  26435. choice = 1;
  26436. if (menu_type <= 1)
  26437. {
  26438. color_printf("%s",menu_color,menu_first_line);
  26439. gotoxy(2,first_line_loc+1);
  26440. length = values[4] - '0';
  26441. length = 10 * length + values[5] - '0';
  26442.  
  26443. for (indx = 0; indx < menu_second_line_length; indx++)
  26444. if (indx < length)
  26445. putcolorchar(menu_second_line[indx],
  26446. highlight_color);
  26447. else
  26448. putcolorchar(menu_second_line[indx],
  26449. menu_color);
  26450. }
  26451. else
  26452. {
  26453. color_printf(screen_first_line,menu_color);
  26454. gotoxy(2,first_line_loc+1);
  26455. color_printf(screen_second_line,menu_color);
  26456. }
  26457. choices = 10 * (values[O] - '0') + values[1] -'0';
  26458. gotoxy(column,row);
  26459. for(;;)
  26460. {
  26461. key_id = getch();
  26462. if (key_id == 0)
  26463. key_id = getch()+256;
  26464. if (menu_type <= 1)
  26465. {
  26466. switch(key_id)
  26467. {
  26468. case 13:
  26469. if(choice > select_no)
  26470. goto ExitPoint;
  26471. change_line_color(47);
  26472. gotoxy(2,23);
  26473. color_printf("%s",screen_color,spaces);
  26474. gotoxy(2,23);
  26475. color_printf("%s",menu_color,
  26476. screen first_line);
  26477. gotoxy(2,24);
  26478. color_printf("%s",screen_color,spaces);
  26479. gotoxy(2,24);
  26480. color_printf("%s",menu_color,
  26481. screen_second_line);
  26482. gotoxy(column,row);
  26483. menu_type +=2;
  26484. break;
  26485. case 333: /*Right Arrow*/
  26486.  
  26487. choice = choice + 2;
  26488. case 331: /*Left Arrowb*/
  26489. --choice;
  26490. if (choice < 1)
  26491. choice = choices;
  26492. if (choice > choices)
  26493. choice = 1;
  26494. start = 10 * (values[(choice-1)*4+2] - '0')
  26495. +values[(choice-1)*4+3] - '0';
  26496. length = 10 * (values[(choice-1)*4+4] - '0')
  26497. +values[(choice-1)*4+5] - '0';
  26498. gotoxy(2,24);
  26499. for (indx = 0;indx < menu_second_line_length;
  26500. indx++)
  26501. {
  26502. if ((indx >= start) && (indx < start
  26503. + length))
  26504.  putcolorchar
  26505.  (menu_second_line
  26506.  [indx],
  26507.  highlight_color);
  26508. else
  26509.  putcolorchar
  26510.  (menu_second_line
  26511.  [indx],
  26512.  menu_color);
  26513. }
  26514. gotoxy (column, row);
  26515. break;
  26516. default:
  26517. if ((key_id >= 0x41) && (key_id <= 0x7A))
  26518. {
  26519. temp = toupper(key_id);
  26520. for (indx=0; indx<=choices; indx++)
  26521. {
  26522.  start = 10 * (values[
  26523.  (indx-1)*4+2] -
  26524.  '0')+values[(indx
  26525.  -1)*4+3] - '0';
  26526.  if (temp == menu_second_line
  26527.  [start])
  26528.  {
  26529.  choice = indx;
  26530.  if(choice >
  26531.  select_no)
  26532.  goto ExitPoint;
  26533.  change_line_color
  26534.  (47);
  26535.  gotoxy(2,23);
  26536.  color_printf("%s",
  26537.  screen_color,spaces);
  26538.  gotoxy(2,23);
  26539.  color_printf("%s",
  26540.  menu_color,
  26541.  screen_first_line);
  26542.  gotoxy(2,24);
  26543.  color_printf("%s",
  26544.  screen_color,spaces);
  26545.  gotoxy(2,24);
  26546.  
  26547.  color_printf("%s",
  26548.  menu_color,
  26549.  screen_second_line);
  26550.  gotoxy(column,row);
  26551.  menu_type += 2;
  26552.  }
  26553. }
  26554. }
  26555. }
  26556. }
  26557. else
  26558. {
  26559. if (key_id == escape_char)
  26560. goto ExitPoint;
  26561. switch(key_id)
  26562. {
  26563. case 8: /*Backspace*/
  26564. case 331: /*Left Arrow*/
  26565. if ((menu_type == 0) 
  26566. (menu_type == 2))
  26567.  change_line_color
  26568.  (screen_color);
  26569. do
  26570. {
  26571.  column--;
  26572.  if (column < 0)
  26573.  {
  26574.  column = 79;
  26575.  row --;
  26576.  if (row < 0)
  26577.  row = 24;
  26578.  }
  26579.  indx = column/8;
  26580.  remainder = column - indx*8;
  26581.  interim = map[row] [indx] &
  26582.  (0x01 << remainder);
  26583. }
  26584. while (interim == 0x00);
  26585. gotoxy (column,row );
  26586. if ((menu_type == 0) 
  26587. (menu_type == 2))
  26588.  change_line_color
  26589.  (highlight_color);
  26590. break;
  26591. case 333: / *Right Arrow*/
  26592. if ((menu_type == 0) 
  26593. (menu_type == 2))
  26594.  change_line_color
  26595.  (screen_color);
  26596. do
  26597. {
  26598.  column++;
  26599.  if (column > 79)
  26600.  {
  26601.  column = 0;
  26602.  row ++;
  26603.  if (row > 24)
  26604.  row = 0;
  26605.  }
  26606.  
  26607.  indx = column/8;
  26608.  remainder = column - indx*8;
  26609.  interim = map[row] [indx] &
  26610.  (0x01 << remainder);
  26611. }
  26612. while (interim == 0x00);
  26613. gotoxy(column,row);
  26614. if ((menu_type == 0) 
  26615. (menu_type == 2))
  26616.  change_line_color
  26617.  (highlight_color);
  26618. break;
  26619. case 13:
  26620. column = 0;
  26621. case 336: /*Down Arrow*/
  26622. if ((menu_type == 0) 
  26623. (menu_type == 2))
  26624.  change_line_color
  26625.  (screen_color);
  26626. row++;
  26627. if (row > 24)
  26628.  row = 0;
  26629. indx = column/8;
  26630. remainder = column - indx * 8;
  26631. interim = map[row][indx] & (0x01 <<
  26632. remainder);
  26633. if (interim !=0x00)
  26634. {
  26635.  gotoxy(column,row);
  26636.  if ((menu_type == 0) 
  26637.  (menu_type == 2))
  26638.  change_line_color
  26639.  (highlight_color);
  26640.  break;
  26641. }
  26642. column = 0;
  26643. do
  26644. {
  26645.  indx = column/8;
  26646.  remainder = column - indx*8;
  26647.  interim = map[row][indx] &
  26648.  (0x01 << remainder);
  26649.  if (interim != 0x00)
  26650.  {
  26651.  gotoxy(column,row);
  26652.  if ((menu_type == 0)
  26653.  ((menu_type == 2))
  26654.  change_line_color
  26655.  (highlight_color);
  26656.  break;
  26657.  }
  26658.  column++;
  26659.  if (column > 79)
  26660.  {
  26661.  column = 0;
  26662.  row ++;
  26663.  if (row > 24)
  26664.  row = 0;
  26665.  }
  26666.  
  26667. }
  26668. while (interim == 0x00);
  26669. break;
  26670. case 328: /*Up Arrow*/
  26671. if ((menu_type == 0) 
  26672. (menu_type == 2))
  26673.  change_line_color
  26674.  (screen_color);
  26675. row--;
  26676. if (row < 0)
  26677.  row = 24;
  26678. indx = column/8;
  26679. remainder = column - indx * 8;
  26680. interim = map[row][indx] & (0x01
  26681. << remainder);
  26682. if (interim != 0x00)
  26683. {
  26684.  gotoxy(column,row);
  26685.  if ((menu_type == 0) 
  26686.  (menu_type == 2))
  26687.  change_line_color
  26688.  (highlight_color);
  26689.  break;
  26690. }
  26691. column = 79;
  26692. do
  26693. {
  26694.  indx = column/8;
  26695.  remainder = column - indx*8;
  26696.  interim = map[row][indx] &
  26697.  (0x01 << remainder);
  26698.  if (interim != 0x00)
  26699.  {
  26700.  gotoxy(column,row);
  26701.  if ((menu_type == 0)
  26702.  (menu_type == 2))
  26703.  change_line_color
  26704.  (highlight_color);
  26705.  break;
  26706.  }
  26707.  column--;
  26708.  if (column < 0)
  26709.  {
  26710.  column = 79;
  26711.  row --;
  26712.  if (row < 0)
  26713.  row = 24;
  26714.  }
  26715. }
  26716. while (interim == 0x00);
  26717. break;
  26718. default:
  26719. if ((menu_type == 1) 
  26720. (menu_type == 3))
  26721. {
  26722.  putcolorchar(key_id,
  26723.  screen_color);
  26724.  do
  26725.  {
  26726.  
  26727.  column++;
  26728.  if (column > 79)
  26729.  {
  26730.  column = 0;
  26731.  row ++;
  26732.  if (row > 24)
  26733.  row =
  26734.  0;
  26735.  }
  26736.  indx = column/8;
  26737.  remainder = column -
  26738.  indx * 8;
  26739.  interim = map[row]
  26740.  [indx] & (0x01
  26741.  << remainder);
  26742.  }
  26743.  while (interim == 0x00);
  26744.  gotoxy(column,row);
  26745. }
  26746. }
  26747. }
  26748. }
  26749. ExitPoint:
  26750. return choice;
  26751. }
  26752.  
  26753. /*
  26754. change_line color = changes the color of a line up to a double space
  26755. */
  26756.  
  26757. void change_line_color(int color)
  26758. {
  26759. union REGS rin;
  26760. char prev_char;
  26761.  
  26762. prev_char = 's';
  26763. for( ; ; )
  26764. {
  26765. rin.h.ah = 3;
  26766. rin.h.bh = 0;
  26767. int86(0x10,&rin,&rin);
  26768. ch = read_char_from_screen();
  26769. if((ch == ' ') && (prev_char == ' '))
  26770. break;
  26771. prev_char = ch;
  26772. gotoxy(rin.h.dl,rin.h.dh);
  26773. putcolorchar(ch,color);
  26774. }
  26775. gotoxy(column,row);
  26776. }
  26777.  
  26778. /*
  26779. clearscreen = clears the screen and displays selected color background
  26780. */
  26781.  
  26782. void clearscreen(int color)
  26783. {
  26784. int indx;
  26785. union REGS reg;
  26786.  
  26787.  
  26788. gotoxy(0,0);
  26789. reg.h.ah = 9;
  26790. reg.h.al = 0x20;
  26791. reg.h.bh = 0;
  26792. reg.h.bl = color;
  26793. reg.x.cx = 2000;
  26794. int86(0x10,®,®);
  26795. }
  26796.  
  26797. /*
  26798. color_printf = printf with selected foreground and background colors
  26799. */
  26800.  
  26801. void color_printf (char *msg,int color,...)
  26802. {
  26803. union REGS reg;
  26804. char ch, string[2000];
  26805. int i = 0;
  26806. va_list (ap);
  26807.  
  26808. va_start (ap,msg);
  26809. vsprintf(string,msg,ap); /*do printf to string*/
  26810. va_end(ap);
  26811. while ((ch=string[i++]) != '\0') /*get chars from string till end*/
  26812. {
  26813. if (ch == 0x0A) /*is character a line feed*/
  26814. {
  26815. reg.h.ah = 3;
  26816.  
  26817. int86(0x10,®,®);
  26818. /*get cursor position*/
  26819. reg.h.d1 = 0;
  26820. reg,h.dh++;
  26821. /*cursor value to beginning of next line*/
  26822. reg.h.ah = 2;
  26823. int86(0x10,®,®);
  26824. /*set new cursor position*/
  26825. }
  26826. else
  26827. {
  26828. reg.h.ah = 9;
  26829. reg.h.al = ch;
  26830. reg.x.bx = color;
  26831. reg.x.cx = 1;
  26832. int86(0x10,®,®);
  26833. /*send a color character to display*/
  26834. reg.h.ah = 3;
  26835. int86(0x10,®,®);
  26836. /*get cursor position in D reg*/
  26837. reg.x.dx++;
  26838. reg.h.ah = 2;
  26839. /*increment cursor position value*/
  26840. int86(0x10,®,®);
  26841. /*set cursor to new position*/
  26842. }
  26843. }
  26844. }
  26845.  
  26846.  
  26847. /*
  26848. gotoxy = moves cursor to selected column and row
  26849. */
  26850.  
  26851. void gotoxy(int col, int row)
  26852. {
  26853. union REGS reg;
  26854. reg.h.ah = 2;
  26855. reg.h.bh = 0;
  26856. reg.x.dx = (row << 8) +col;
  26857. int86(0X10,®, ®);
  26858. }
  26859.  
  26860. /*
  26861. putcolorchar = displays a character with selected color foreground
  26862. and background
  26863. */
  26864.  
  26865. void putcolorchar(char character, int color)
  26866. {
  26867. union REGS reg;
  26868. reg.h.ah = 3;
  26869. reg.h.bh = 0;
  26870. int86(0x10,®,®);
  26871. reg.h.ah = 9;
  26872. reg.h.al = character;
  26873. reg.h.bl = color;
  26874. reg.x.cx = 1;
  26875. int86(0x10,®,®);
  26876. reg.h.ah =2;
  26877. reg.h.dl = reg.h.dl+1;
  26878. int86(0x10,®,®);
  26879. }
  26880.  
  26881. /*
  26882. read_char_from_screen = reads a character from the screen into 'ch'
  26883. */
  26884.  
  26885. char read_char_from_screen()
  26886. {
  26887. char ch;
  26888. union REGS reg;
  26889.  
  26890. reg.h.ah = 3;
  26891. reg.h.bh = 0;
  26892. int86(0x10,®,®);
  26893. reg.h.ah. = 8;
  26894. int86(0x10,®,®);
  26895. ch = reg.h.al;
  26896. attr = reg.h.ah;
  26897. reg.h.ah. = 2;
  26898. reg.h.dl = reg.h.dl+1;
  26899. int86(0x10,®,®);
  26900. return ch;
  26901. }
  26902.  
  26903. /*
  26904. set_cursor = sets up array of permissible cursor positions
  26905. */
  26906.  
  26907.  
  26908. void set_cursor()
  26909. {
  26910. int i,j,indx,remainder,key_value;
  26911. char interim,map[25][10];
  26912.  
  26913. FILE *f1;
  26914. f1 = fopen("matrix.c","w");
  26915.  
  26916. for(i=0;i<=24;i++)
  26917. {
  26918. for(j=0;j<=9++)
  26919. {
  26920. map[i][j]=0x00;
  26921. }
  26922. }
  26923. row=0;
  26924. column = 0;
  26925. gotoxy(column,row);
  26926. while((key_value = getch()) != 13)
  26927. {
  26928. if (key_value == 0)
  26929. key_value = getch()+128;
  26930. switch(key_value)
  26931. {
  26932. case 8: /*Backspace*/
  26933. case 203: /*Left Arrow*/
  26934. --column;
  26935. break;
  26936. case 205: /*Right Arrow*/
  26937. ++column;
  26938. break;
  26939. case 208: /*Down Arrow*/
  26940. ++row;
  26941. break;
  26942. case 200: /*Up Arrow*/
  26943. --row;
  26944. break;
  26945. default:
  26946. putch(key_value);
  26947. column++;
  26948. }
  26949. if (column > 79)
  26950. {
  26951. column = 0;
  26952. row++;
  26953. }
  26954. if (column = < 0>
  26955. {
  26956. column = 0;
  26957. row--;
  26958. }
  26959. if (row < 0)
  26960. row = 0;
  26961. gotoxy(column,row);
  26962. }
  26963. row=0;
  26964. column=0;
  26965. while (row*column < 1896)
  26966.  
  26967. {
  26968. gotoxy(column,row);
  26969. ch = read_char_from_screen();
  26970. if ((ch == 'x') (ch == 'X'))
  26971. {
  26972. indx = column/8;
  26973. remainder = column - indx *8;
  26974. interim = 0x01;
  26975. interim = interim << remainder;
  26976. map[row][indx] = map[row][indx] interim;
  26977. }
  26978. column++;
  26979. if (column > 79)
  26980. {
  26981. column = 0;
  26982. row++;
  26983. }
  26984. gotoxy(column,row);
  26985. }
  26986.  
  26987. clearscreen(30);
  26988. fputc('{',f1);
  26989. gotoxy(0,0);
  26990. for (i=0;i<=24;i++)
  26991. {
  26992. for (j=0;j<=9;j++)
  26993. {
  26994. fprintf(f1,"0x%x",map[i][j]);
  26995. if ((i != 24) (j != 9))
  26996. fputc(',',f1);
  26997. }
  26998. fprintf(f1,"\n");
  26999. }
  27000. fputc('}',f1);
  27001. fclose(f1);
  27002. }
  27003. /* End of File */
  27004.  
  27005.  
  27006.  
  27007.  
  27008.  
  27009.  
  27010.  
  27011.  
  27012.  
  27013.  
  27014.  
  27015.  
  27016.  
  27017.  
  27018.  
  27019.  
  27020.  
  27021.  
  27022.  
  27023.  
  27024.  
  27025.  
  27026.  
  27027.  
  27028.  
  27029.  
  27030. Multiple Copy Math Functions
  27031.  
  27032.  
  27033. Timothy Prince
  27034.  
  27035.  
  27036. Timothy Prince has a B.A. in physics from Harvard and a Ph.D. in mechanical
  27037. engineering from the University of Cincinnati. He has 25 years of experience
  27038. in aerodynamic design and computation. He can be contacted at 452 Palmitas
  27039. St., Solana Beach, CA 92075.
  27040.  
  27041.  
  27042. Pipelined architectures including vector and superscalar obtain their speed by
  27043. depending in part on working on independent calculations in pipeline fashion.
  27044. Most computationally intensive applications present enough opportunities for
  27045. parallel or pipeline operation to make worthwhile increases in speed. The
  27046. normal use of scalar math functions, which produce a single result, poses an
  27047. obstacle to superscalar performance. These functions can be organized to
  27048. increase the internal opportunities for parallelism as compared with optimum
  27049. scalar processor code. Performance remains far short of the potential, unless
  27050. more parallelism is exploited by working on more than one math function result
  27051. at a time. A further reason for obtaining multiple results is that the
  27052. overhead for calling functions which take less than 10 microseconds becomes
  27053. excessive.
  27054. Soon after the introduction of vector computers, vector math functions were
  27055. introduced to provide vector performance in calculations involving such
  27056. functions. With pipelined or superscalar processors, vector functions may be
  27057. effective, but functions which calculate a small number of copies per call are
  27058. more versatile. Since a typical RISC architecture employs a four-stage
  27059. pipeline, functions which calculate two or four copies should be enough to
  27060. maximize performance.
  27061. Vector chunk math functions may be used whether or not your compiler makes
  27062. specific provision for them. I will show actual examples of the C code of such
  27063. functions. The functions sin_4 (four sins), cosf_sinf_2 (two pairs of float
  27064. sin and cos), powf_2 (two float pows), and tan_2 (two tans) are chosen for
  27065. their proven usefulness and because they illustrate the points which I want to
  27066. make.
  27067.  
  27068.  
  27069. Vector vs Superscalar Function Calls
  27070.  
  27071.  
  27072. On a vector architecture, vector math functions naturally are performed on
  27073. argument vectors, and normal vector performance is not approached except on
  27074. long vectors. These architectures perform well when 50 or more functions are
  27075. to be calculated at a time. Suppose we wanted to integrate a function
  27076. involving sin and cos by Simpson's rule, producing a loop such as
  27077. for(i=2 ; i<n ; i += 2){
  27078. yint += q[i-2]*sin(t[i-2])
  27079. +4*q[i-1]*sin(t[i-1])+q[i]*sin(t[i]);
  27080. xint += q[i-2]*cos(t[i-2])
  27081. +4*q[i-1]*cos(t[i-1])+q[i]*cos(t[i]);
  27082. }
  27083. which involves n evaluations of sin and cos. A vector compiler would start out
  27084. by setting up the six vectors made up of the three sin and cos evaluations
  27085. from each copy of the loop body. Almost a third of these evaluations would be
  27086. duplicates, since the vector of sin(t[i-2]) is the same as the vector of
  27087. sin(t[i]) except that sin(t[0]) and sin(t[n-1]) are not repeated. Each of
  27088. these vectors has length (n+1)/2, so n would have to be around 100 before good
  27089. vector performance could be achieved.
  27090. In order to approach the performance potential of a vector architecture, we
  27091. would have to rewrite the code to store the q[]*sin() and q[]*cos()
  27092. intermediate results in vectors in a preliminary loop, and then add the
  27093. appropriate values in another loop. Even after 20 years of vector compiler
  27094. development, this is more analysis than any compiler can do without human
  27095. assistance. Most reasonable attempts to improve the performance of this loop
  27096. for a scalar architecture will prevent vectorization, and changes to improve
  27097. vector performance will reduce scalar or superscalar performance. Although
  27098. vector compilers now deal well with sum reductions such as this loop, this is
  27099. done in effect by splitting the vectors again into six to 10 shorter vectors,
  27100. making a vector architecture less than fully effective for this type of
  27101. application.
  27102. Unrolling compilers can eliminate most of the duplicate operations by
  27103. combining the common subexpressions over several iterations of the loop. Each
  27104. additional loop iteration will require an additional pair of sins and coss.
  27105. Compilers have been available (e.g. Multiflow) which would detect this
  27106. situation automatically and build in a call such as cosf_sinf_2(t[0],t[1])
  27107. which returns cosf and sinf of both arguments, a total of four results from
  27108. one function call. Such a function is a good match to the architecture of a
  27109. superscalar processor. Even if your compiler does not perform the
  27110. transformation automatically, manual rewriting is not unduly burdensome and
  27111. need not detract from performance on scalar processors. A change as simple as
  27112. ty = q[0]*sinf(t[0]);
  27113. tx = q[0]*cosf(t[0]);
  27114. for( i=2; i<n; i+=2){
  27115. temp = cosf_sinf_2(t[i-1],t[i]);
  27116. yint += ty+q[i]*temp.sin2+4*q[i-1]*temp.sin1;
  27117. xint += tx+q[i]*temp.cos2+4*q[i-1]*temp.cos1;
  27118. tx = q[i]*temp.cos2;
  27119. ty = q[i]*temp.sin2;
  27120. }
  27121. should produce most of the advertised performance of any non-vector machine.
  27122.  
  27123.  
  27124. Vector and Vector Chunk Function Coding Style
  27125.  
  27126.  
  27127. Multiple copy or vector chunk math functions, like vector code, need to be
  27128. written without conditionals which actually cause transfer of control. In a
  27129. scalar trig function, it would usually be worth while to test the argument to
  27130. find out whether it needs to be translated into the primary range. In a vector
  27131. chunk function, the full range reduction should be performed whether it is
  27132. needed or not, so that all of the code for the function can be compiled as one
  27133. basic block.
  27134. Transfer of control (branching) gives the compiler a choice of undesirable
  27135. consequences. Either the pipelines must be allowed to empty, reducing the
  27136. performance to scalar levels until they are refilled, or trace scheduling must
  27137. be used to fill the pipeline with future operations along the preferred path
  27138. of execution. Each branch can cause generation of another trace, and the
  27139. length of compiled code may grow exponentially with the number of branches.
  27140. The speed gained by keeping the pipelines full may be canceled by increased
  27141. paging.
  27142. A great deal of progress has been made in architecture and compilers in recent
  27143. years, so that many simple conditional selections can be performed without a
  27144. transfer of control, if this is necessary to keep a processor producing
  27145. results at rated speed. This requires calculation of both alternative results
  27146. followed by instructions which select the correct one. There is a good
  27147. correspondence with the syntax of ?: in C, although the compiler should not be
  27148. totally dependent on the programmer choosing to use ?. Existing compilers do
  27149. not vectorize if..else. All of the operations in the sin, cos, tan, exp, and
  27150. log functions can and should be written in vectorizable form, even when the
  27151. overall scheme is to favor superscalar execution.
  27152. Vector chunk functions do not fit well with the <errno.h> system for error
  27153. reporting. The best that can be done is to report that ERANGE or EDOM
  27154. exceptions have been raised for one of the arguments or results. Vector
  27155. functions give an even hazier indication of trouble.
  27156.  
  27157.  
  27158. Some Nuts and Bolts of Machine Dependence
  27159.  
  27160.  
  27161. In some of the examples, the sign of a float or double is tested by assuming
  27162. that it is in the same position as the sign of an int which shares the same
  27163. storage. This is done either because it is faster or because it reduces
  27164. register thrashing on certain machines. Generally, in these functions, there
  27165. is an imbalance of double over integer arithmetic, and integer operations can
  27166. be treated as a free resource whenever float operations are being performed at
  27167. the same time.
  27168. This code will work as is on most modern architectures which use the same byte
  27169. order for double and int. On VAX-compatible architectures, the sign of a
  27170. double falls in place with the sign of a short at the same address, apparently
  27171. as a result of the PDP-11 ancestry.
  27172. A few architectures also suffer from excess of divide and multiply operations
  27173. over add and subtract, so, in the examples, addition is used instead of
  27174. multiply by 2. In these examples, it occurs when there are plenty of pipeline
  27175. slots open, but in other cases, one would not want to prevent an optimizing
  27176. compiler from converting multiply by 2 to a ldexp operation.
  27177. As the conditionals which would be required for architectures not conforming
  27178. to IEEE P754 standards would clutter up the code, I have simply put in #error
  27179. preprocessor directives, which are ignored by non-ANSI compilers because they
  27180. are indented.
  27181. Since a good pipelining compiler will give priority to finishing up the
  27182. expressions which are placed first in the code, the later copies of the
  27183. functions tend to fall behind. This may be aggravated by compilers which give
  27184. priority to loading constants into registers long before they can be used. The
  27185. way to compensate is to write the earlier copies so as to minimize use of
  27186. registers and leave more empty pipeline slots which can be filled up by
  27187. arithmetic from the later copies.
  27188. The later copies are written for more available parallelism at the expense of
  27189. register usage, so that, when the calculation of the earlier results has
  27190. finished, the pipelines can still be kept full nearly to the end of the
  27191. function. This can lead to somewhat greater round off errors in the later
  27192. copies, in the double functions. In the float functions, use of double
  27193. arithmetic eliminates the effect of order of operations on accuracy.
  27194.  
  27195. Systems which are unable to perform simple conditional selections without
  27196. branching may require sign changes to be performed by xoring the sign bit. To
  27197. avoid branching, errno may be left alone or set by
  27198. errno=ERANGE&(-(relational expresssion))
  27199. which sets it to zero or to ERANGE. This is contrary to the normal requirement
  27200. that errno never be set to zero, but may be a satisfactory compromise.
  27201.  
  27202.  
  27203. Calculation of Coefficients
  27204.  
  27205.  
  27206. Listing 1 shows a bc program for calculation of coefficients for sin, as used
  27207. in sin_4.c. Running it with double arithmetic in C will produce the same
  27208. results up through at least 10 digits. Because bc uses fixed point arithmetic,
  27209. it needs extra fractional digits for sin, more than are needed for most
  27210. problems. The same program will work if the t function is replaced by a(x)/x,
  27211. with appropriate changes in the interval. The coefficients for log base 2,
  27212. used in powf_2.c, are calculated by having the t function evaluate the
  27213. appropriate Taylor-Maclaurin series. With overnight runs, bc can calculate
  27214. coefficients up to 50 significant digits. These Chebyshef subroutines are
  27215. adaptations of those given by Press, Flannery et al (1).
  27216.  
  27217.  
  27218. Multiple Copy sin
  27219.  
  27220.  
  27221. Listing 2 shows the four copy sin function. The code which performs range
  27222. reduction, by subtracting off the nearest multiple of pi, uses a rint
  27223. function, but takes advantage of the fact that dividing by pi does not change
  27224. the sign. It assumes that addition is performed in the highest available
  27225. precision, which may be more than double. rint is not covered by standards,
  27226. and its result may depend on rounding mode, so it would not take care of
  27227. portability. Use of long double precision in these operations is highly
  27228. desirable, but of little value unless a true long double value of pi is
  27229. available. long double should prevent degradation of accuracy for arguments up
  27230. to pi*10^ (LDBL_DIG-DBL_DIG).
  27231. The sign of the argument is ored into the rounding constant in order not to
  27232. tie up as many double registers, so that the operations on subsequent copies
  27233. will not be delayed. This procedure avoids branching on processors which do
  27234. not have a select operation. Portability at the expense of speed can be
  27235. obtained using expressions such as
  27236. pm = (int)(x/pi+(x>O?.5:-.5))
  27237. or
  27238. pm = (int)(x/pi-.5+(x>0))
  27239. since, if fabs(x/pi) exceeds INT_MAX, there probably aren't more than three
  27240. digits significance left. Since FORTRAN and Pascal have round double to
  27241. integer syntax, certain processors (e.g. MIPS) have implemented it as a single
  27242. instruction, which is not used by C compilers.
  27243. The integer overflow situation is reported as errno=ERANGE, without
  27244. distinguishing which of the four arguments caused it. Non-portable code for
  27245. testing the exponent field to identify this situation is used because, on the
  27246. system where the code was tested, there weren't enough double registers to
  27247. squeeze in any more standard arithmetic without stretching the code out by
  27248. 30%. There are ways to test whether pm is odd without ever casting to int, so
  27249. that range errors are avoided out to pi/DBL_EPSILON, but it's not worth the
  27250. trouble.
  27251. Covering the whole interval from -pi/2 to pi/2 with a single curve fit avoids
  27252. conditional branches which are particularly troublesome for vector or vector
  27253. chunk coding. An eight term Chebyshef-economized polynomial is just sufficient
  27254. to hold the errors to 1 unit-in-the-last-place with DBL_MANT_DIG = 53, in the
  27255. absence of other approximations. Putting the interval end points where the
  27256. function has zero slope helps prevent round off error from introducing
  27257. discontinuities.
  27258. Horner polynomial evaluations are begun before the sign of the result has been
  27259. determined, leaving the sign switching to be performed when the compiler finds
  27260. the necessary pipeline slots. The third and fourth polynomial evaluations will
  27261. lag well behind the first and second, so the third and fourth Horner
  27262. polynomials are split in two so that the pipelines can be kept fuller after
  27263. the earlier polynomial evaluations are complete. This adds two multiplications
  27264. and one possibly significant round off error in each of the third and fourth
  27265. results.
  27266. The fourth copy differs from the third only in that the code is written with
  27267. parentheses to force the final additions to occur in the most parallel (but
  27268. not most accurate) order. The dummy multiply by 1 is needed to force K&R
  27269. compilers to honor the parentheses, but has no effect in ANSI syntax. Since
  27270. similar techniques are used to a greater extent in scalar math libraries for
  27271. superscalar processors, these less accurate results are likely to be closer to
  27272. the scalar results.
  27273. This function should achieve a megaflop rating better than the LIN-PACK rating
  27274. on many processors, which is unusual effectiveness for such complex code. One
  27275. of the ways it could be used would be to combine calculation of unrelated sins
  27276. and coss, using the relationship
  27277. #define cos(x) sin(PI/2-(x))
  27278. as needed. A similar tactic should pay off on vector architectures, in which
  27279. the various arguments are copied to a temporary vector so that the vector sin
  27280. function can be used.
  27281. Effective pipelining of this function appears to require more than 16 double
  27282. registers, along with special efforts to perform as many calculations as
  27283. possible in int registers. Examination of results of an early MIPS compiler
  27284. showed that it was able to economize on the size of generated code by setting
  27285. the constants only once. Like many RISC architectures, MIPS has immediate
  27286. constants available only to initialize registers, not to participate directly
  27287. in floating point operations. This may not leave enough registers available
  27288. for extensive pipelining.
  27289. Optimization for reduction of length of generated code prior to scheduling of
  27290. operations is less well correlated with execution speed on pipelined than on
  27291. scalar processors. The MIPS software does not report the number of empty
  27292. pipeline stages. The compiler for the original Multiflow 7/200 compiles this
  27293. code in 96 major instruction cycles and obtains a superscalar speedup factor
  27294. of 4. Only six of these instructions are empty, all occurring after the first
  27295. copy result is complete. sin_4 on the Multiflow is twice as fast as their
  27296. library sin, giving four results in 12 microseconds. On the Silicon Graphics
  27297. 4D/25, both sin_4 and the library sin take about four microseconds per result.
  27298. Listing 3 shows a test driver to compare the results of sin_4 with sin. While
  27299. many compilers allow passing a double to a function which receives it as a
  27300. union, other compilers push a union on the stack in a different order from a
  27301. plain double. It is safer to make sin_4 copy the arguments into its unions. On
  27302. one of the compilers tested, the generated code is the same either way.
  27303. The Chebyshef fit of Listing 1 can be changed to use sin_4, after changing
  27304. from bc to C syntax. The order of Chebyshef fit may as well be a multiple of
  27305. 4. The accuracy of math function approximations, such as the functions
  27306. discussed in this article, can be tested by fitting Chebyshef polynomials and
  27307. comparing the coefficients with those obtained by a higher accuracy
  27308. calculation in the same interval.
  27309.  
  27310.  
  27311. Multiple Copy Float cos and sin
  27312.  
  27313.  
  27314. Listing 4 shows a function to calculate cos and sin of two arguments in float
  27315. precision. Since it uses rational polynomial approximations, there is more
  27316. built-in opportunity for parallelism than in a Horner polynomial, and two such
  27317. functions are enough to fill a four stage pipeline at the peak stages. Without
  27318. prototypes, the only way to pass float arguments without widening to double is
  27319. by unions. With prototyping, it would be better to pass float arguments and
  27320. copy them to unions inside the function.
  27321. One multiply can be eliminated from the critical path by scaling the arguments
  27322. to multiples of pi/2 and adjusting the polynomial coefficients accordingly.
  27323. The division by 2 of the half-angle formulae is buried in the coefficients, so
  27324. the range reduction maps the arguments into the range -2 to +2. Adding and
  27325. subtracting 4/LDBL_EPSILON produces a number which is rounded to the nearest
  27326. multiple of 4. As long as promotion to IEEE double is used, so that no
  27327. precautions against underflow are needed, there would be no problem in
  27328. changing the scaling so that the code could start off
  27329. tn = x1.flt/2/PI - rint(x1.flt/2/PI)
  27330. in case that could be calculated more efficiently. The choice of scale was
  27331. influenced by the desire to maintain accuracy if base 16 arithmetic is used.
  27332. Scaling the arguments would produce an additional round off error if the
  27333. calculations were performed in float precision, but double is almost mandatory
  27334. anyway as it prevents degradation of accuracy for arguments up to
  27335. 2pi*10^(DBL_DIG-FLT_DIG). A warning such as storing a value into errno could
  27336. be provided when larger arguments arrive, but this is not clearly a failure
  27337. meriting the ERANGE label unless the argument becomes so large that the rint
  27338. code won't work. Basing the errno calculation on values which are calculated
  27339. anyway minimizes the use of additional registers.
  27340. The first rational polynomial is calculated Horner style, and the last
  27341. attempts to catch up by calculating all terms individually, at the cost of one
  27342. additional multiplication. The scheme of eliminating one of the coefficients
  27343. by choice of scale allows the numerator to get a head start so that the final
  27344. multiplication can be performed without delaying the division. The compiler
  27345. may have to be forced into performing the first add in the denominator without
  27346. waiting until the last term has been calculated. Certain compilers insist on
  27347. converting the repeated divisions into multiplications, which is no problem
  27348. when the operations have been promoted in precision.
  27349.  
  27350.  
  27351. Vector Chunk float pow
  27352.  
  27353.  
  27354. The pow function in C is expected to embody two entirely different types of
  27355. operation. In order for it to be vectorizable, or to obtain good vector chunk
  27356. performance enhancement with current compilers, it has to be restricted to the
  27357. cases of positive base, where it can be replaced in effect by
  27358. #define pow(x,y) exp(log(x)*y)
  27359. This could be done with a top level powf_2 which determines whether both pairs
  27360. of arguments are of one type, and, if so, invokes an appropriate vector chunk
  27361. function. The usual test is whether y1 and y2 are changed by casting to int
  27362. and back to float. It doesn't hurt much to use the log treatment anyway,
  27363. unless x is negative. If the argument pairs cannot be processed by the same
  27364. algorithm, it would have been more efficient not to have tried to treat them
  27365. as a vector chunk at all.
  27366. The function of Listing 4 does not take care of the negative base case, which
  27367. is OK according to ANSI standards if it is called as the implementation of the
  27368. FORTRAN real exponentiation operator. I use it in this form in time marching
  27369. aerodynamics codes, where it gets executed millions of times.
  27370. Promotion to double is really needed only in the sections involving addition
  27371. of the integer exponent to the base range log2 up to the splitting of the exp2
  27372. argument into an integer plus or minus a fraction, and then only when the
  27373. result is far from 1. The somewhat complicated system for splitting the base
  27374. into modified frexp form works quickly and accurately without widening on a
  27375. system without gradual underflow. On architectures such as VAX which use a
  27376. different byte order for float and int, the unions and constants are
  27377. different.
  27378. If gradual underflow is to be supported without widening the precision, it
  27379. will require special case treatment. To reduce degradation of accuracy if
  27380. widening is not used for addition of the integer and fraction parts of the
  27381. log2 function, log2 should be split into a power of 2 plus a smaller term.
  27382. This leads to complicated code which may require branching, thus defeating
  27383. attempts to gain pipelined performance.
  27384. Evaluation of log2(x2)*y2 is speeded up by grouping the terms in pairs. The
  27385. calculation log2(x2)*y1 then becomes a bottleneck until the multiplication by
  27386. y1 is distributed onto the two groups, one of which consists of the three-term
  27387. Horner polynomial. Multiplication of y1 by the integer exponent is performed
  27388. well before it is needed.
  27389. Making such detailed adjustments for a given system is possible only with
  27390. readable assembly language which displays the final scheduling of the
  27391. pipelined operations, and is helped greatly by static profiling which gives
  27392. the effective clock count for each block of generated code. Since we try to
  27393. write these functions so that there is only one code block, and there are few
  27394. memory accesses which could introduce bus delays, the speed will not depend on
  27395. data and there should be no question what effect each change has on speed.
  27396. In order to make the ROUND macro work the same under K&R syntax as it would in
  27397. ANSI C, dummy multiplications by 1 are introduced. Otherwise it is a matter of
  27398. luck whether a K&R compiler will generate the required code, although the left
  27399. associativity of the + and - operators should produce a preference for left to
  27400. right evaluation. From an algebraic point of view, ROUND would do nothing, and
  27401. AI techniques could conceivably allow a compiler to know this. The peculiar
  27402. syntax of K&R which requires such multiplications by 1 makes it
  27403. semi-obligatory for optimizing compilers to eliminate the redundant operation,
  27404. unless compiling for an architecture which may generate faster code with
  27405. alternating multiplication and addition.
  27406. If the compiler is unable to generate efficient code for the max and min
  27407. macros, it would be better to perform the ldexp operations on doubles and hope
  27408. that the extra range of double will take care of over and underflows.
  27409.  
  27410.  
  27411.  
  27412. Multiple Copy tan
  27413.  
  27414.  
  27415. The tan_2 function (Listing 5) requires the least non-portable coding for
  27416. optimum results, but it illustrates optimizations which have not appeared in
  27417. the functions discussed above.
  27418. Range reduction consists simply of subtracting the nearest multiple of pi, and
  27419. there is no advantage in playing games with unions. The comparisons start into
  27420. the pipeline first and are completed before the divides, which may have been
  27421. converted to multiplications by the compiler.
  27422. The remainder of the calculation consists of evaluation of a rational
  27423. polynomial. On a machine with a divide which pipelines at the same intervals
  27424. as the other operations, straightforward Horner evaluation of the numerators
  27425. and denominators might work as well as anything. On the processor for which
  27426. this code was tuned, a divide operation delays the pipeline, but does not
  27427. affect addition. For this reason, the order of operations is set up to push
  27428. all of the multiplications into the pipeline before the divides, as well as to
  27429. start the first division as soon as possible.
  27430. The terms of the numerator and denominator are grouped so that operations are
  27431. always ready to start into the pipeline, and to take advantage of
  27432. architectures which have separate pipelines for multiplication and addition.
  27433. In the denominator of the first copy, the high order terms must be added in
  27434. order of increasing complexity. This could be done with parentheses with an
  27435. ANSI compiler. Possibly better accuracy could be obtained by adding the high
  27436. order term last. Order is not dictated in the low order terms of the first
  27437. copy, so as to avoid delaying the second copy. In the numerator of the second
  27438. copy, the final multiplications are distributed so that they may be performed
  27439. before the final addition, in order to get the multiplications out of the way
  27440. early, as well as to allow the second calculation to begin to catch up with
  27441. the first.
  27442.  
  27443.  
  27444. Summary
  27445.  
  27446.  
  27447. Much of this article will appeal only to those who like to tweak code for
  27448. another 20% in performance. I have concentrated on the points where
  27449. architectural dependencies pop up and tried to show where their impact can be
  27450. reduced for relatively small performance penalties. Math library functions are
  27451. probably the closest thing to applications where non-portable code is
  27452. appropriate. This is the reason for these functions (at least the scalar
  27453. versions) being defined in the C standard so that they need not be carried as
  27454. part of on application.
  27455. The extent to which special vector chunk functions should be used to perform
  27456. math library operations in groups may be questioned. An application which uses
  27457. these functions probably should provide an alternative header file which will
  27458. cause them to be replaced with standard functions. Compilers are most likely
  27459. to begin to incorporate such functions automatically if they produce benefits
  27460. on the standard benchmarks.
  27461. Scalar math functions can detract from the performance of superscalar
  27462. processors. The techniques shown enable superscalar performance to be obtained
  27463. in the evaluation of grouped math library functions. In typical applications,
  27464. the percentage of execution time spent in math functions can be reduced in
  27465. comparison with a scalar processor.
  27466. References
  27467. John Ellis, Bulldog: A Compiler for VLIW Architectures, MIT Press, 1985
  27468. (avoidance of code explosion, trace scheduled pipelined code).
  27469. Press, Flannery, Teukolsky, Vettering, Numerical Recipes in C, Cambridge, 1988
  27470. for Chebyshef analysis.
  27471. John Palmer, Stephen Morse, The 8087 Primer, Wiley, 1984 for some math
  27472. function concepts.
  27473. P. J. Plauger, Jim Brodie, Standard C, Microsoft Press, 1989 for the simplest
  27474. satisfactory explanation of float.h, limits.h, math.h.
  27475. T. Prince, "Generating Source for <float.h>," The C Users Journal, V8N6, June
  27476. 1990.
  27477.  
  27478. Listing 1 (s)
  27479. /*
  27480. ** bc program to calculate Chebyshef economized polynomial
  27481. ** for evaluation of sin(x) */
  27482. /* use bc -1 to get c() and s() functions */
  27483. define t(x) { /* sin(x)/x */
  27484. if(x==0)return(1.); /* derivative of s function */
  27485. return (s(x) / x); /* put function to be fit here */ }
  27486. define b(x) {
  27487. if (x < 0) return (-x);
  27488. return (x); }
  27489. define m(x, y) {
  27490. if (x > y) return (x);
  27491. return (y); }
  27492. n = 22; /* number of Chebyshef terms */
  27493. scale = 40;
  27494. p = a(1.) * 4; /* pi */
  27495. b = p * .5; /* upper end of curve fit interval */
  27496. a = -b; /* lower end of interval */
  27497. /* chebft adapted from Press Flannery et al */
  27498. /* "Numerical Recipes" FORTRAN version */
  27499. for (k = 1; k <= n; ++k) {
  27500. c[k] = 0;
  27501. f[k] = t(c((k - .5) * p / n) * (b - a) * .5 + (b + a) * .5);
  27502. }
  27503. /* because of symmetry, even c[] are 0 */
  27504. for (j = 1; j <= n; j += 2) {
  27505. s = 0;
  27506. q = (j - 1) * p / n;
  27507. for (k = 1; k <= n; ++k) s += c(q * (k - .5)) * f[k];
  27508. (c[j] = 2 / n * s); }
  27509. /* skip even terms, which are 0 */
  27510. for (n = 5; n <= 19; n += 2) {
  27511. /* chebpc */
  27512. for (j = 1; j <= n; ++j) d[j] = e[j] = 0;
  27513. d[1] = c[n];
  27514.  
  27515. for (j = n - 1; j >= 2; -j) {
  27516. for (k = n - j + 1; k >= 2; -k) {
  27517. s = d[k];
  27518. d[k] = d[k - 1] * 2 - e[k];
  27519. e[k] = s; }
  27520. s = d[1];
  27521. d[1] = c[j] - e[1];
  27522. e[1] = s; }
  27523. for (j = n; j >= 2; -j) d[j] = d[j - 1] - e[j];
  27524. d[1] = c[1] * .5 - e[1];
  27525. /* pcshft */
  27526. g = 2 / (b - a);
  27527. for (j = 2; j <= n; ++j) {
  27528. d[j] *= g;
  27529. g *= 2 / (b - a); }
  27530. for (j = 1; j < n; ++j) {
  27531. h = d[n];
  27532. for (k = n - 1; k >= j; -k) {
  27533. h = d[k] - (a + b) * .5 * h;
  27534. d[k] = h; }
  27535. }
  27536. "Chebyshev Sin fit x<Pi/2 coefficients"
  27537. " Maximum Rel Error:"
  27538. m(b(c[n + 2]), b(c[2])) / t(b);
  27539. for (i = 1; i <= n; i += 2) d[i];
  27540. }
  27541. /* End of File */
  27542.  
  27543.  
  27544. Listing 2 (sin_4.c)
  27545. typedef struct {
  27546. double X1, X2, X3, X4;
  27547. } ARG_D_4; /* vector 4*/
  27548. #include "float.h"
  27549. ARG_D_4 sin_4(xx1, xx2, xx3, xx4)
  27550. double xx1, xx2, xx3, xx4;
  27551. /* use where cos of same argument not needed
  27552. ** 16 digits precision, compare to 15 digits in "dtrig.h"
  27553. ** T C Prince */
  27554. {
  27555. union dblfmt {
  27556. double dbl;
  27557. int idbl;
  27558. struct dfmt { /* IEEE p754 */
  27559. unsigned int sign:1;
  27560. unsigned int ex:11;
  27561. } fmt;
  27562. } xi1;
  27563. double xr, x2, x4, x8;
  27564. #ifdef _STDC_____LINEEND____
  27565. long double pi = 3.1415926535897932385, pml;
  27566. #include <limits.h>
  27567. #else
  27568. register double pi = 3.1415926535897932385, pml;
  27569. #define LONG_MIN 0x80000000
  27570. #endif
  27571. union dblfmt pm, round;
  27572. ARG_D_4 res;
  27573. #define BIAS DBL_MAX_EXP
  27574.  
  27575. #include <errno.h>
  27576. #ifndef errno
  27577. extern int errno;
  27578. #endif
  27579. #define ODD(i) ((i)&1)
  27580. /* use identity sin(x + n pi) = (-1)^n sin(x)
  27581. ** to reduce range to -pi/2 < x < pi/2
  27582. ** pml=rint(xi1/pi) */
  27583. #if FLT_ROUNDS != 1
  27584. #error "rounding mode not nearest; adjust code"
  27585. #endif
  27586. #if FLT_RADIX !=2 && FLT_RADIX != 10
  27587. #error "code not optimum for accuracy in this RADIX"
  27588. #endif
  27589. #if DBL_DIG > 16
  27590. #error "more terms needed for full accuracy"
  27591. #endif
  27592. /* shortcut test of sign, not portable to VAX */
  27593. round.dbl = 1 / LDBL_EPSILON;
  27594. xi1.dbl = xx1;
  27595. round.idbl = xi1.idbl & LONG_MIN;
  27596. pml = xx1 / pi + round.dbl;
  27597. /* sign reversal may reduce register usage */
  27598. xr = pi * (pml -= round.dbl) - xx1;
  27599. /* shortcut test for fabs(pml) > INT_MAX */
  27600. pm.dbl = pml;
  27601. if (pm.fmt.ex > BIAS +31)
  27602. errno = ERANGE;
  27603. /* don't wait to calculate xr**2 until sign is fixed;
  27604. ** another sign reversal is due if pm.dbl is odd */
  27605. x2 = xr * xr;
  27606. /* first sign reversal compensated in coefficient signs;
  27607. ** conditional sign fixed by testing odd/even
  27608. ** first two results are obtained by straight Horner
  27609. ** polynomial evaluation */
  27610. res.X1 = (-.9999999999999999 + x2 * (.1666666666666607
  27611. + x2 * (-.833333333328281e-2 + x2 * (.19841269824875e-3
  27612. + x2 * (-.2755731661057e-5 + x2 * (.25051882036e-7
  27613. + x2 * (-.160481709e-9 + x2 * .7374418e-12)))))))
  27614. * (ODD((int) pm.dbl) ? -xr : xr);
  27615. /* sin(xi2) */
  27616. round.dbl = 1 / LDBL_EPSILON;
  27617. xi1.dbl = xx2;
  27618. round.idbl = xi1.idbl & LONG_MIN;
  27619. pml = xx2 / pi + round.dbl;
  27620. xr = pi * (pml -= round.dbl) - xx2;
  27621. pm.dbl = pml;
  27622. if (pm.fmt.ex > BIAS + 31)
  27623. errno = ERANGE;
  27624. x2 = xr * xr;
  27625. res.X2 = (-.9999999999999999 + x2 * (.1666666666666607
  27626. + x2 * (-.833333333328281e-2 + x2 * (.19841269824875e-3
  27627. + x2 * (-.2755731661057e-5 + x2 * (.25051882036e-7
  27628. + x2 * (-.160481709e-9 + x2 * .7374418e-12)))))))
  27629. * (ODD((int) pm.dbl) ? -xr : xr);
  27630. /* sin(xi3) */
  27631. round.dbl = 1 / LDBL_EPSILON;
  27632. xi1.dbl = xx3;
  27633. round.idbl = xi1.idbl & LONG_MIN;
  27634.  
  27635. pml = xx3 / pi + round.dbl;
  27636. xr = pi * (pml -= round.dbl) - xx3;
  27637. pm.dbl = pml;
  27638. if (pm.fmt.ex > BIAS + 31)
  27639. errno = ERANGE;
  27640. x2 = xr * xr;
  27641. x4 = x2 * x2;
  27642. /* split into 2 Horner polynomials to increase
  27643. ** parallelism after 1st result finishes */
  27644. res.X3 = (-.9999999999999999 + x2 * (.1666666666666607
  27645. + x2 * (-.833333333328281e-2
  27646. + x2 * .19841269824875e-3))
  27647. + (-.2755731661057e-5 + x2 * (.25051882036e-7
  27648. + x2 * (-.160481709e-9
  27649. + x2 * .7374418e-12))) * x4 * x4) *
  27650. (ODD((int) pm.dbl) ? -xr : xr);
  27651. /* sin(xi4) */
  27652. round.dbl = 1 / LDBL_EPSILON;
  27653. xi1.dbl = xx4;
  27654. round.idbl = xi1.idbl & LONG_MIN;
  27655. pml = xx4 / pi + round.dbl;
  27656. xr = pi * (pml -= round.dbl) - xi1.dbl;
  27657. /* errno is set to ERANGE if any of the arguments are too
  27658. ** large for reasonable range reduction */
  27659. pm.dbl = pml;
  27660. if (pm.fmt.ex > BIAS + 31)
  27661. errno = ERANGE;
  27662. x2 = xr * xr;
  27663. x4 = x2 * x2;
  27664. x8 = x4 * x4;
  27665. /* multiply by 1 is K&R way to enforce parentheses */
  27666. res.X4 = ((-.9999999999999999 + x2 * (.1666666666666607
  27667. + x2 * (-.833333333328281e-2
  27668. + x2 * .19841269824875e-3))) * 1
  27669. + (-.2755731661057e-5 + x2 * (.25051882036e-7
  27670. + x2 * (-.160481709e-9 + x2 * .7374418e-12))) * x8)
  27671. * (ODD((int) pm.dbl) ? -xr : xr);
  27672. return res;
  27673. }
  27674. /* End of File */
  27675.  
  27676.  
  27677. Listing 3 (sin_4~bt.c)
  27678. /* Tests sin_4() */
  27679. typedef struct {
  27680. double X1, X2, X3, X4;
  27681. } ARG_D_4; /* vector 4 */
  27682. ARG_D_4 sin_4();
  27683. #include <math.h>
  27684. main(){
  27685. ARG_D_4 res;
  27686. res=sin_4(-2.,-1.,1.,2.);
  27687. printf(
  27688. "\t%.17g\t%.17g\n\t%.17g\t%.17g\n\t%.17g\t%.17g\n\t%.17g\t%.17g\n",
  27689. res.X1,sin(-2.),
  27690. res.X2,sin(-1.),
  27691. res.X3,sin(1.),
  27692. res.X4,sin(2.));
  27693. }
  27694.  
  27695. /* End of File */
  27696.  
  27697.  
  27698. Listing 4 (powf_2.c)
  27699. typedef struct {
  27700. float X1, X2;
  27701. } ARG_F_2; /* vector 2 */
  27702. #include "float.h"
  27703. #include <errno.h>
  27704. #ifndef errno
  27705. extern int errno;
  27706. #endif
  27707. #define MANTBITS (FLT_MANT_DIG -1)
  27708. ARG_F_2 powf_2(xi1, y1, xi2, y2)
  27709. union fltfmt {
  27710. float flt;
  27711. int iflt; /* VAX: must change all this */
  27712. struct ffmt {
  27713. unsigned int ex:9;
  27714. unsigned int mant:MANTBITS;
  27715. } fmt;
  27716. } xi1, xi2, y1, y2;
  27717. {
  27718. #define max(i,j) ((i)>(j)?(i):(j))
  27719. #define min(i,j) ((i)<(j)?(i):(j))
  27720. #if FLT_MANT_DIG != 24
  27721. #error "use portable frexp() ldexp() */
  27722. #endif
  27723. #if FLT_ROUNDS == 1
  27724. #if defined(_STDC_)
  27725. /* This works on some non-ANSI compilers */
  27726. #define ROUND(x) ((x)>=0?( \
  27727. (x)+1/LDBL_EPSILON)-1/LDBL_EPSILON: \
  27728. ((x)-1/LDBL_EPSILON)+1/LDBL_EPSILON)
  27729. #else
  27730. #define ROUND(x) ((x)>=0?( \
  27731. (x)+1/LDBL_EPSILON)*1-1/LDBL_EPSILON: \
  27732. ((x)-1/LDBL_EPSILON)*1+1/LDBL_EPSILON)
  27733. #endif
  27734. #else
  27735. #define ROUND(x) ((x)>=0?(int)(x+.5):(int)(x-.5))
  27736. #endif
  27737. int mi, mi2, msign;
  27738. double xr, x2, r, r1;
  27739. ARG_F_2 res;
  27740. /* Copy 1 */
  27741. /* This frexp() operation would be done better after
  27742. ** promotion to double
  27743. ** but it's not mandatory unless dealing with gradual
  27744. ** underflow;
  27745. ** it would eliminate most cases of 0 and Inf changing
  27746. ** to finite numbers
  27747. ** if((xi1.flt=frexp(xi1.flt ,&mi))<sqrt(.5)){
  27748. --mt;
  27749. xi1.flt *= 2;
  27750. } */
  27751. mi = ((xi1.iflt & 0x7fffffff) -
  27752. (mi2 = (xi1.fmt.mant < 0x3504f3 ?
  27753. (2 - FLT_MIN_EXP) << MANTBITS :
  27754.  
  27755. (1 - FLT_MIN_EXP) << MANTBITS)))
  27756. >> MANTBITS;
  27757. if (xi1.iflt < 0 xi2. iflt < 0) errno = EDOM;
  27758. xi1.iflt = mi2 xi1.fmt.mant;
  27759. /* Mult by y distributed to increase parallelism */
  27760. r1 = (xr = (xi1.flt - 1) / (xi1.flt + 1)) * y1.flt;
  27761. x2 = xr * xr;
  27762. /* Coefficients determined by Chebyshef fitting
  27763. ** double precision is only really needed from here */
  27764. r = y1.flt * (double) mi + r1 * (2.8853904 +
  27765. x2 * (.5958 * x2 + .961588));
  27766. /* Msign = (r -= rint(r)) <0 */
  27767. msign = (r -= r1 = ROUND(r)) < 0;
  27768. r *= 125.0718418 + (x2 = r * r);
  27769. x2 = 360.8810526 + 17.3342336 * x2;
  27770. /* Xi1.flt = ldexp((x2+r) / (x2-r),(int)r1) */
  27771. xi1.flt = (x2 + r) / (x2 - r);
  27772. /* Preferably do this ldexp() operation in double,
  27773. ** but it's slower,
  27774. ** even though msign can be eliminated;
  27775. ** it would always give Inf rather than NaN
  27776. ** and would allow use of gradual underflow */
  27777. xil.iflt += (max(FLT_MIN_EXP - 2 + msign,
  27778. min(FLT_MAX_EXP + msign, (int) r1)) << MANTBITS);
  27779. /* X.fmt.ex+=mi; with limiting to prevent exponent wraparound */
  27780. res. X1 = xi1.flt;
  27781. /* Copy 2 */
  27782. mi = ((xi2.iflt & 0x7fffffff) -
  27783. (mi2 = (xi2.fmt.mant < 0x3504f3 ?
  27784. (2 - FLT_MIN_EXP) << MANTBITS :
  27785. (1 - FLT_MIN_EXP) << MANTBITS)))
  27786. >> MANTBITS;
  27787. xi2.iflt = mi2 xi2.fmt.mant;
  27788. r1 = (xr = (xi2.flt - 1) / (xi2.flt + 1)) * y2.flt;
  27789. r1 *= x2 = xr * xr;
  27790. r = y2.flt * ((double) mi + xr * 2.8853904) +
  27791. r1 * (.5958 * x2 + .961588);
  27792. msign = (r -= r1 = ROUND(r)) < 0;
  27793. r *= 125.0718418 + (x2 = r * r);
  27794. x2 = 360.8810526 + 17.3342336 * x2;
  27795. xi2.flt = (x2 + r) / (x2 - r);
  27796. xi2.iflt += (max(FLT_MIN_EXP - 2 + msign,
  27797. min(FLT_MAX_EXP + msign, (int) r1)) << MANTBITS);
  27798. res.X2 = xi2.flt;
  27799. return res;
  27800. }
  27801. /* End of File */
  27802.  
  27803.  
  27804. Listing 5 (cosf_~bs.c)
  27805. typedef struct {
  27806. float cos1, sin1, cos2, sin2;
  27807. } ARG_FF_2; /* vector 2 pairs */
  27808. ARG_FF_2 cosf_sinf_2(x1, x2)
  27809. union {
  27810. float flt;
  27811. int iflt;
  27812. } x1, x2;
  27813. { /* 2 pair single precision
  27814.  
  27815. sin/cos function */
  27816. #include "float.h"
  27817. #if FLT_ROUNDS != 1
  27818. #error "rounding mode not nearest, fix code"
  27819. #endif
  27820. #include <errno.h>
  27821. #ifndef errno
  27822. extern int errno;
  27823. #endif
  27824. #include <math.h>
  27825. #define M_2_PI 0.63661977236758134308
  27826. #define T2PI 2*M_2_PI
  27827. #define TP2 T2PI*T2PI
  27828. #define TP3 TP2*T2PI
  27829. #define TP4 TP2*TP2
  27830. #define TP5 TP3*TP2
  27831. ARG_FF_2 res;
  27832. double tn, td, r;
  27833. /* integer compare with 0 same as float, for IEEE
  27834. ** since arg comes in int register, this is faster
  27835. **
  27836. ** doing everything in double, we won't lose accuracy
  27837. ** by converting arg to multiple of PI/2
  27838. ** this allows range reduction by subtracting an integer
  27839. **
  27840. ** reduce to range +- 2, divide by 2 later
  27841. ** when it cannot underflow */
  27842. tn = x1.flt * M_2_PI + (td = x1.iflt >= 0 ?
  27843. 4 / LDBL_EPSILON : -4 / LDBL_EPSILON);
  27844. if (fabs(x1.flt * M_2_PI) >= 4 / LDBL_EPSILON 
  27845. fabs(x2.flt * M_2_PI) >= 4 / LDBL_EPSILON)
  27846. errno = ERANGE;
  27847. tn -= td;
  27848. tn = x1.flt * M_2_PI - tn;
  27849. td = tn * tn;
  27850. /* divide arg by 2 and rationalize numerator and denominator
  27851. ** numerator of rational approx for tan(x1/2)
  27852. ** Horner polynomials 1st time */
  27853. tn *= 886.77348 * TP4 + td * (-99.398954 * TP2 + td);
  27854. /* denominator */
  27855. td = 886.77346 * TP5 + td *
  27856. (-394.98971 * TP3 + td * 14.425694 * T2PI);
  27857. /* cos, sin half angle formulae, rationalized */
  27858. res.cos1 = (td * td - tn * tn) / (td * td + tn * tn);
  27859. res.sin1 = (tn * td + tn * td) / (td * td + tn * tn);
  27860. /* copy 2 */
  27861. tn = x2.flt * M_2_PI + (td = x2.iflt >= 0 ?
  27862. 4 / LDBL_EPSILON : -4 / LDBL_EPSILON);
  27863. tn -= td;
  27864. tn = x2.flt * M_2_PI - tn;
  27865. td = tn * tn;
  27866. /* distribute terms to finish polynomials quicker */
  27867. tn *= 886.77348 * TP4 - td * 99.398954 * TP2 + td * td;
  27868. r = 886.77346 * TP5 - td * 394.98971 * TP3;
  27869. td = r + td * td * 14.425694 * T2PI;
  27870. res.cos2 = (td * td - tn * tn) / (td * td + tn * tn);
  27871. res.sin2 = (tn * td + tn * td) / (td * td + tn * tn);
  27872. return res;
  27873. }
  27874.  
  27875.  
  27876. /* End of File */
  27877.  
  27878.  
  27879. Listing 6 (tan_2.c)
  27880. typedef struct {
  27881. double X1, X2;
  27882. } ARG_D_2; /* vector 2 */
  27883. ARG_D_2
  27884. tan_2(xi1, xi2)
  27885. double xi1, xi2;
  27886. {
  27887. double x2, x, n1, x4;
  27888. ARG_D_2 res;
  27889. #include "float.h"
  27890. #if FLT_ROUNDS != 1
  27891. #error "rounding mode not nearest; adjust code"
  27892. #endif
  27893. #if FLT_RADIX !=2 && FLT_RADIX != 10
  27894. #error "code not optimum for accuracy in this
  27895. RADIX"
  27896. #endif
  27897. #include <errno.h>
  27898. #ifndef errno
  27899. extern int errno;
  27900. #endif
  27901. #include <math.h>
  27902. #define M_PI 3.14159265358979323846
  27903. x2 = (n1 = (xi1 > 0 ? 1 / LDBL_EPSILON :
  27904. -1 / LDBL_EPSILON))7 + (x = xi1) / M_PI;
  27905. x -= (x2 - n1) * M_PI;
  27906. if (fabs(xi1 / M_PI) >= 1 / LDBL_EPSILON 
  27907. fabs(xi2 / M_PI) >= 1 / LDBL_EPSILON)
  27908. errno = ERANGE;
  27909. /* now in 1st or 4th quadrant */
  27910. #define c0 33281881.3202530279
  27911. n1 = c0 + (x2 = x * x) * (-15666569.8711211851);
  27912. x4 = x2 * x2;
  27913. res.X1 = x * (c0 + x2 * (-4572609.43103684572) + x4 *
  27914. (131095.887915363619 + x2 * (-968.863245687503149 +
  27915. x2))) / (n1 + x4 * (915701.668921990803
  27916. + x2 * (-13491.7937027796916)
  27917. + x4 * 44.4083322286368691));
  27918. /* copy 2 */
  27919. x2 = (n1 = (xi2 > 0 ? 1 / LDBL_EPSILON :
  27920. -1 / LDBL_EPSILON)) + (x = xi2) / M_PI;
  27921. x -= (x2 - n1) * M_PI;
  27922. n1 = 915701.668921990803 - (x2 = x * x)
  27923. * 13491.7937027796916;
  27924. x4 = x2 * x2;
  27925. res.X2 = (x * (c0 + x2 * (-4572609.43103684572)) +
  27926. (131095.887915363619 + x2 * (-968.863245687503149 +
  27927. x2)) * x4 * x) / (c0 + x2 * (-15666569.8711211851) +
  27928. x4 * (n1 + x4 * 44.4083322286368691));
  27929. return res;
  27930. }
  27931. /* End of File */
  27932.  
  27933.  
  27934.  
  27935.  
  27936.  
  27937.  
  27938.  
  27939.  
  27940.  
  27941.  
  27942.  
  27943.  
  27944.  
  27945.  
  27946.  
  27947.  
  27948.  
  27949.  
  27950.  
  27951.  
  27952.  
  27953.  
  27954.  
  27955.  
  27956.  
  27957.  
  27958.  
  27959.  
  27960.  
  27961.  
  27962.  
  27963.  
  27964.  
  27965.  
  27966.  
  27967.  
  27968.  
  27969.  
  27970.  
  27971.  
  27972.  
  27973.  
  27974.  
  27975.  
  27976.  
  27977.  
  27978.  
  27979.  
  27980.  
  27981.  
  27982.  
  27983.  
  27984.  
  27985.  
  27986.  
  27987.  
  27988.  
  27989.  
  27990.  
  27991.  
  27992.  
  27993.  
  27994.  
  27995.  
  27996.  
  27997.  
  27998. Yet Another C++ Money Class
  27999.  
  28000.  
  28001. Adolfo Di Mare
  28002.  
  28003.  
  28004. This article is not available in electronic form.
  28005.  
  28006.  
  28007.  
  28008.  
  28009.  
  28010.  
  28011.  
  28012.  
  28013.  
  28014.  
  28015.  
  28016.  
  28017.  
  28018.  
  28019.  
  28020.  
  28021.  
  28022.  
  28023.  
  28024.  
  28025.  
  28026.  
  28027.  
  28028.  
  28029.  
  28030.  
  28031.  
  28032.  
  28033.  
  28034.  
  28035.  
  28036.  
  28037.  
  28038.  
  28039.  
  28040.  
  28041.  
  28042.  
  28043.  
  28044.  
  28045.  
  28046.  
  28047.  
  28048.  
  28049.  
  28050.  
  28051.  
  28052.  
  28053.  
  28054.  
  28055.  
  28056.  
  28057.  
  28058.  
  28059. Lexical Analysis Using Search Tries
  28060.  
  28061.  
  28062. John W. M. Stevens
  28063.  
  28064.  
  28065. John Stevens is a graduate of Colorado State University with a bachelor's
  28066. degree in computer science. He has worked at five different companies as a
  28067. programmer and software engineer, writing everything from accounting programs
  28068. for truck drivers to C compilers for high-speed parallel computers. He is
  28069. currently working for Space Tech as a compiler writer.
  28070.  
  28071.  
  28072. Recently, there has been a lot of talk about an old UNIX idea, that of user
  28073. programmability. User programming presents some drawbacks, not least of which
  28074. is the absence of a standard language. Each new program requires the user to
  28075. learn a new language (unless the new program is a clone of another). Also,
  28076. though different user languages may have similiar syntax, they may not
  28077. interpret a statement the same way. In addition, user programming languages
  28078. have, until just recently, been cryptic and difficult to learn.
  28079.  
  28080.  
  28081. Language Interpreters
  28082.  
  28083.  
  28084. To create a program that is user-programmable requires that the software
  28085. engineer know how to design, write, and maintain a language interpreter. Such
  28086. arts are taught in most computer science curriculums. A language
  28087. interpretation system contains three basic components: the lexical analyzer,
  28088. the parser, and the interpreter.
  28089. The first part of the system, the lexical analyzer, takes ASCII input,
  28090. separates it into words, and converts those words to numeric values, called
  28091. tokens. Words that have special meaning in the language are called keywords.
  28092. Punctuation characters, such as ; and :, also have special meaning in the
  28093. language and must be tokenized as well. The lexical analyzer determines if the
  28094. input contains illegal words or punctuation characters.
  28095. While the lexical analyzer breaks the input text into words, it does not
  28096. determine whether the words are arranged into legal sentences. This is the job
  28097. of the parser. The parser takes a stream of tokens from the lexical analyzer
  28098. and attempts to determine if they form a stream of legal sentences according
  28099. to the language's grammar. A grammar consists of a set of rules that describe
  28100. all legal sentences possible in the language. Not all legal sentences make
  28101. sense. In most programming languages, the parser will accept legal sentences
  28102. that the interpreter cannot understand or execute.
  28103. Once the parser has decided that the token stream forms legal sentences, the
  28104. interpreter combines the operations of semantic analysis and program
  28105. execution. Semantic analysis determines what operations the program is telling
  28106. the interpreter to execute. Execution is the operation of reading program
  28107. tokens and translating them into a series of machine language function calls
  28108. that instruct the CPU what to do. Interpreted languages execute slower than
  28109. compiled languages, in part, because the interpreter must translate each
  28110. program sentence into machine language every time it is executed. Sentences
  28111. from compiled programs are already translated into machine language.
  28112.  
  28113.  
  28114. Lexical Analyzers And Search Tries
  28115.  
  28116.  
  28117. To facilitate the construction of lexical analyzers, I use a special class of
  28118. search tree, called a trie. A trie is a tree data structure that allows
  28119. strings with similiar character prefixes to use the same prefix data and store
  28120. only the tails as separate data. One character of the string is stored at each
  28121. level of the tree, with the first character of the string stored at the root,
  28122. and the last character of the string stored at a sub-tree node or in a leaf
  28123. node. Figure 1 shows how a trie would store the following collection of words:
  28124. ape, append, able, bee, bearing, cape, caper, capable, us, use and user.
  28125. Tries used for lexical analysis store token values with each character in the
  28126. trie, as shown in Listing 1. Most of the token values are zero, indicating
  28127. ILLEGAL KEYWORD. The token value of the last character of each legal word is
  28128. the token value for that word. For example, in Figure 1, the struct for the
  28129. letter e in the trie branch under the letter u stores the token value for the
  28130. word USE. The letters s and r in the same branch would have the tokens US and
  28131. USER stored with them, respectively.
  28132. Using a trie in a lexical analyzer combines the operations of breaking the
  28133. input text into words and determining whether or not the words are legal for
  28134. the language. This scheme imposes language design constraints on the engineer,
  28135. since words do not have to be delimited to be recognized. The constraint is
  28136. either to design a requirement for delimitation into the language definition
  28137. or to ensure that no two adjacent words of the language can ever be combined
  28138. to make a longer, legal word of the language.
  28139.  
  28140.  
  28141. Static Tries In C Arrays
  28142.  
  28143.  
  28144. I prefer to store my tries as static data in the same file as the code for the
  28145. lexical analyzer. This arrangement allows each program to use more than one
  28146. lexical analyzer, as well as eliminates the need for external files. On the
  28147. other hand, tries have a widely variable number of elements per trie level,
  28148. making it imperative to use a dynamically-sized data structure. The method I
  28149. adopted stores each level of the trie as a uniquely named array with elements
  28150. corresponding to the structure in Listing 1.
  28151. Storing tries as source code in the lexical analyzer can make for very large
  28152. source files. Even a small language can have a search trie that is 1100 lines
  28153. of source code. Such a file compiles to a relatively small amount of data, but
  28154. just as no program is ever fast enough, no program is ever small enough
  28155. either. Roughly half to two-thirds of the memory that a trie uses is for
  28156. storing pointers to sub-tries. To minimize the memory requirements of a search
  28157. trie after compilation, you should exploit your compiler's options to group
  28158. data together. Doing so lets you use a smaller pointer size to reference that
  28159. data.
  28160. In order to make creating and maintaining lexical analyzers that use tries
  28161. easier, I've written a program that accepts a text file of token words and
  28162. token define names, creates the trie in memory, and dumps the trie to standard
  28163. out as static C data arrays. This scheme facilitates adding or deleting a word
  28164. from the trie.
  28165.  
  28166.  
  28167. Example Lexical Analyzer
  28168.  
  28169.  
  28170. The example lexical analyzer uses a trie that contains the keywords and token
  28171. values defined in Listing 2. The first word on the line is the keyword, and
  28172. the second word is the token value enumeration label for that keyword. The
  28173. trie creation program processes this file. The program output is captured in a
  28174. file that will be included in the source code file for the lexical analyzer.
  28175. Listing 3 contains the token value enumeration, function prototypes, and type
  28176. definitions necessary to use the lexical analyzer. To increase the readability
  28177. of the parser source code, I've selected enumeration labels that are as
  28178. similar as possible to the keywords they represent. Because of name space
  28179. collision with type and/or define names used by the C compiler, some of the
  28180. enumeration labels are postfixed with the string_T.
  28181. Listing 4 contains the lexical analyzer. I've extracted this code from a
  28182. program that acts as a user-programmable file selection shell. The function
  28183. OpenPrg() initializes the lexical analyzer by opening the file that contains
  28184. the source code to be analyzed. The parser then calls the function Lex()
  28185. repeatedly to get tokens. Each time Lex() is called, it begins by reading and
  28186. throwing away both white space characters (space, tab and newline characters)
  28187. and comments. When the first character of a suspected keyword is found, it
  28188. breaks out of the loop and attempts to get either a string constant, integer
  28189. constant, time, or date.
  28190. If the input is not a constant of some type, the trie search function is
  28191. called. The function TrieSrch() begins by attempting to find the input
  28192. character in the trie node. TrieSrch() accepts a pointer to a node of a trie,
  28193. a character to search for, and a pointer to a buffer for storing the word read
  28194. from the input file. The function uses a binary search because the characters
  28195. in a trie node are stored in sorted order.
  28196. If the input character is found in the trie node, TrieSrch() saves it in the
  28197. word buffer. If the matching character in the trie node has a pointer to a
  28198. child trie node, TrieSrch() reads another character from the file and calls
  28199. itself recursively. If the return value from the recursive call indicates that
  28200. the character was not found, TrieSrch() assumes that the input character for
  28201. this call was the last character of a legal word and unreads the character
  28202. read for the recursive call. The token value of the input character to this
  28203. call is returned.
  28204. If the matching character does not have a pointer to a child trie node, the
  28205. keyword buffer is NUL-terminated and the token value stored with the matching
  28206. character is returned. If the input character is not found in the trie node,
  28207. TrieSrch() NUL-terminates the keyword buffer and returns a value indicating
  28208. that the character was not found.
  28209. Figure 2 presents an algorithm in structured English for separating words from
  28210. an input character stream and searching for them in a search trie.
  28211.  
  28212.  
  28213. Summary
  28214.  
  28215.  
  28216. A trie is probably not the most efficient data structure for determining the
  28217. legality of an input word. A sorted table of strings searched with a binary
  28218. search would probably be faster and more memory efficient.
  28219. So why use a trie if it isn't as fast or efficient as other methods? Since the
  28220. hardest part of writing a lexical analyzer is in breaking an undifferentiated
  28221. stream of input characters into words, the beauty of a trie is that it groups
  28222. characters into words and determines their legality at the same time. It is
  28223. also, in my opinion, a more elegant solution. This alone is reason enough for
  28224. me to use a trie.
  28225. Figure 1 Example Search Trie
  28226. Figure 2
  28227. 1) Read a character from the file.
  28228.  
  28229.  Call step 2 with the pointer to the root of the trie, the
  28230.  character read and a pointer to the begining of the key word
  28231.  save buffer.
  28232.  
  28233. 2) Search for the input character in the current node of the trie.
  28234.  If the input character is found in the trie then
  28235.  Save the input character in the key word save buffer.
  28236.  Attempt to read a character from the file.
  28237.  If End of File then
  28238.  Return End of File.
  28239.  If the character found has a child trie pointer then
  28240.  Call Step 2 with the child trie pointer, the character
  28241.  read from the file and a pointer to the next byte in the
  28242.  key word save buffer.
  28243.  If the return value from the call to step 2 is NOT FOUND then
  28244.  Unread the character read from the file
  28245.  Return the token value stored with the input character
  28246.  of this call.
  28247.  else
  28248.  Return the return value of the recursive call.
  28249.  else If the character is not found then
  28250.  Save a NUL in the key word save buffer.
  28251.  Return the value NOT_FOUND.
  28252.  
  28253. Listing 1 NODE Structure
  28254. typedef struct key_st {
  28255. char c; /* String character. */
  28256. TKNS token; /* Token value. */
  28257. struct key_st *child; /* Pointer to sub-trie. */
  28258. } NODE;
  28259.  
  28260.  
  28261. Listing 2
  28262. ( L_PAREN
  28263. ) R_PAREN
  28264. , COMMA
  28265. / F_SLASH
  28266. action ACTION
  28267. after AFTER
  28268. and AND
  28269. archive ARCHIVE
  28270. attributes ATTRIBUTES
  28271. before BEFORE
  28272. directory DIRECTORY_T
  28273. exec EXEC
  28274. files FILES
  28275. hidden HIDDEN
  28276. label LABEL
  28277. modified MODIFIED
  28278. name NAME
  28279. not NOT
  28280. or OR
  28281. print PRINT
  28282. readonly READONLY
  28283. recurs RECURS
  28284. search SEARCH
  28285. select SELECT
  28286. system SYSTEM
  28287. { L_BRACE
  28288.  
  28289.  BAR
  28290. } R_BRACE
  28291.  
  28292.  
  28293. Listing 3
  28294. /***********************************************************************
  28295. * Module : Lexical Analyzer --- Header file containing token value
  28296. * enumeration, type definitions and function prototypes for
  28297. * the lexical analyzer functions.
  28298. *
  28299. * Copyright (C) 1990 John W. M. Stevens, All Rights Reserved
  28300. *
  28301. * Author : John W. M. Stevens
  28302. ***********************************************************************/
  28303.  
  28304. #if ! defined( LEXICAL_ANALYZER )
  28305. #define LEXICAL_ANALYZER 1
  28306.  
  28307. #include <dos.h>
  28308.  
  28309. #define TRUE 1
  28310. #define FALSE 0
  28311. #define ERROR -1
  28312. #define OK 0
  28313.  
  28314. #define PATH_SZ 65
  28315. typedef unsigned int UINT;
  28316. typedef unsigned char UCHAR;
  28317. typedef char PATH[PATH_SZ];
  28318.  
  28319. /* Definition of structure filled in and returned by lex. */
  28320. typedef struct {
  28321. char str[257];
  28322. long no;
  28323. struct time ftime;
  28324. struct date fdate;
  28325. } TOKEN;
  28326.  
  28327. /* Token defines. */
  28328. enum tkn_en {
  28329. STRING = 128,
  28330. NUMBER, TIME, DATE,
  28331.  
  28332. L_PAREN, R_PAREN, COMMA, F_SLASH, ACTION,
  28333. AFTER, AND, ARCHIVE, ATTRIBUTES, BEFORE,
  28334. DIRECTORY_T, EXEC, FILES, HIDDEN, LABEL,
  28335. MODIFIED, NAME, NOT, OR, PRINT,
  28336. READONLY, RECURS, SEARCH, SELECT, SYSTEM,
  28337. L_BRACE, BAR, R_BRACE
  28338. };
  28339. typedef enum tkn_en TKNS;
  28340.  
  28341. /* Function prototypes. */
  28342. extern TKNS Lex(TOKEN *);
  28343. extern void OpenPrg(char *);
  28344. extern void ParsErr(char *);
  28345.  
  28346. #endif
  28347. /* End of File */
  28348.  
  28349.  
  28350.  
  28351. Listing 4
  28352. /***********************************************************************
  28353. * Module : Lexical Analyzer --- Process the input text file into tokens
  28354. * that the parser can understand.
  28355. *
  28356. * Copyright (C) 1990 John W. M. Stevens, All Rights Reserved
  28357. *
  28358. * Routines : Lex - Return the next token from the file.
  28359. * OpenPrg - Open the source file.
  28360. * ParsErr - Report a parsing error.
  28361. *
  28362. * Author : John W. M. Stevens
  28363. ***********************************************************************/
  28364.  
  28365. #include <stdio.h>
  28366. #include <stdlib.h>
  28367. #include <ctype.h>
  28368. #include <string.h>
  28369.  
  28370. #include "lex.h"
  28371.  
  28372. /* Structure of trie branch. */
  28373. typedef struct key_st {
  28374. char c;
  28375. TKNS token;
  28376. struct key_st *child;
  28377. } NODE;
  28378.  
  28379. /* Constants local to this file. */
  28380. #define MAX_STR 256
  28381. #define NOT_FND -2
  28382.  
  28383. /* Object Data. */
  28384. static char word[MAX_STR + 1]; /* Last string analyzed. */
  28385. static char PrvWd[MAX_STR + 1]; /* Previous word. */
  28386. static int LnNo = 0; /* The current line number in the file. */
  28387. static FILE *PrgFl; /* File pointer. */
  28388.  
  28389. /* Trie data structure containing all the keywords and punctuation
  28390. marks for
  28391. * the language being tokenized.
  28392. */
  28393. static
  28394. NODE T5[2]= {
  28395. { ' ', 2, NULL },
  28396. { 'n', ACTION, NULL }
  28397. };
  28398.  
  28399. static
  28400. NODE T4[2] = {
  28401. { ' ', 2, NULL },
  28402. { 'o', 0, T5 }
  28403. };
  28404.  
  28405. static
  28406. NODE T3[2] = {
  28407. { ' ', 2, NULL },
  28408.  
  28409. { 'i', 0, T4 }
  28410. };
  28411.  
  28412. static
  28413. NODE T2[2] = {
  28414. { ' ', 2, NULL },
  28415. { 't', 0, T3 }
  28416. };
  28417.  
  28418. static
  28419. NODE T8[2] = {
  28420. { ' ', 2, NULL },
  28421. { 'r', AFTER, NULL }
  28422. };
  28423.  
  28424. static
  28425. NODE T7[2] = {
  28426. { ' ', 2, NULL },
  28427. ( 'e', 0, T8 }
  28428. };
  28429.  
  28430. static
  28431. NODE T6[2] = {
  28432. { ' ', 2, NULL },
  28433. { 't', 0, T7 }
  28434. };
  28435.  
  28436. static
  28437. NODE T9[2] = {
  28438. { ' ', 2, NULL },
  28439. { 'd', AND, NULL }
  28440. };
  28441.  
  28442. static
  28443. NODE Te[2] = {
  28444. { ' ', 2, NULL },
  28445. { 'e', ARCHIVE, NULL }
  28446. };
  28447.  
  28448. static
  28449. NODE Td[2] = {
  28450. { ' ', 2, NULL },
  28451. { 'v', 0, Te }
  28452. };
  28453.  
  28454. static
  28455. NODE Tc[2] = {
  28456. { ' ', 2, NULL },
  28457. { 'i', 0, Td }
  28458. };
  28459.  
  28460. static
  28461. NODE Tb[2] = {
  28462. { ' ', 2, NULL },
  28463. { 'h', 0, Tc }
  28464. };
  28465.  
  28466. static
  28467. NODE Ta[2] = {
  28468.  
  28469. { ' ', 2, NULL },
  28470. { 'c', 0, Tb }
  28471. };
  28472.  
  28473. static
  28474. NODE T16[2] = {
  28475. { ' ', 2, NULL },
  28476. { 's', ATTRIBUTES, NULL }
  28477. };
  28478.  
  28479. static
  28480. NODE T15[2] = {
  28481. { ' ', 2, NULL },
  28482. { 'e', 0, T16 }
  28483. };
  28484.  
  28485. static
  28486. NODE T14[2] = {
  28487. { ' ', 2, NULL },
  28488. { 't', 0, T15 }
  28489. };
  28490.  
  28491. static
  28492. NODE T13[2] = {
  28493. { ' ', 2, NULL },
  28494. { 'u', 0, T14 }
  28495. };
  28496.  
  28497. static
  28498. NODE T12[2] = {
  28499. { ' ', 2, NULL },
  28500. { 'b', 0, T13 }
  28501. };
  28502.  
  28503. static
  28504. NODE T11[2] = {
  28505. { ' ', 2, NULL },
  28506. { 'i', 0, T12 }
  28507. };
  28508.  
  28509. static
  28510. NODE T10[2] = {
  28511. { ' ', 2, NULL },
  28512. { 'r', 0, T11 }
  28513. };
  28514.  
  28515. static
  28516. NODE Tf[2] = {
  28517. { ' ', 2, NULL },
  28518. { 't', 0, T10 }
  28519. };
  28520.  
  28521. static
  28522. NODE T1[6] = {
  28523. { ' ', 6, NULL },
  28524. { 'c', 0, T2 },
  28525. { 'f', 0, T6 },
  28526. { 'n', 0, T9 },
  28527. { 'r', 0, Ta },
  28528.  
  28529. { 't', 0, Tf }
  28530. };
  28531.  
  28532. static
  28533. NODE T1b[2] = {
  28534. { ' ', 2, NULL },
  28535. { 'e', BEFORE, NULL }
  28536. };
  28537.  
  28538. static
  28539. NODE T1a[2] = {
  28540. { ' ', 2, NULL },
  28541. { 'r', 0, T1b }
  28542. };
  28543.  
  28544. static
  28545. NODE T19[2] = {
  28546. { ' ', 2, NULL },
  28547. { 'o', 0, T1a }
  28548. };
  28549.  
  28550. static
  28551. NODE T18[2] = {
  28552. { ' ', 2, NULL },
  28553. { 'f', 0, T19 }
  28554. };
  28555.  
  28556. static
  28557. NODE T17[2] = {
  28558. { ' ', 2, NULL },
  28559. { 'e', 0, T18 }
  28560. };
  28561.  
  28562. static
  28563. NODE T23[2] = {
  28564. { ' ', 2, NULL },
  28565. { 'y', DIRECTORY_T, NULL }
  28566. };
  28567.  
  28568. static
  28569. NODE T22[2] = {
  28570. { ' ', 2, NULL },
  28571. { 'r', 0, T23 }
  28572. };
  28573.  
  28574. static
  28575. NODE T21[2] = {
  28576. { ' ', 2, NULL },
  28577. { 'o', 0, T22 }
  28578. };
  28579.  
  28580. static
  28581. NODE T20[2] = {
  28582. { ' ', 2, NULL },
  28583. { 't', 0, T21 }
  28584. };
  28585.  
  28586. static
  28587. NODE T1f[2] = {
  28588.  
  28589. { ' ', 2, NULL },
  28590. { 't', 0, T20 }
  28591. };
  28592.  
  28593. static
  28594. NODE T1e[2] = {
  28595. { ' ', 2, NULL },
  28596. { 'e', 0, T1f }
  28597. };
  28598.  
  28599. static
  28600. NODE T1d[2] = {
  28601. { ' ', 2, NULL },
  28602. { 'r', 0, T1e }
  28603. };
  28604.  
  28605. static
  28606. NODE T1c[2] = {
  28607. { ' ', 2, NULL },
  28608. { 'i', 0, T1d }
  28609. };
  28610.  
  28611. static
  28612. NODE T26[2] = {
  28613. { ' ', 2, NULL },
  28614. { 'c', EXEC, NULL }
  28615. };
  28616.  
  28617. static
  28618. NODE T25[2] = {
  28619. { ' ', 2, NULL },
  28620. { 'e', 0, T26 }
  28621. };
  28622.  
  28623. static
  28624. NODE T24[2] = {
  28625. { ' ', 2, NULL },
  28626. { 'x', 0, T25 }
  28627. };
  28628.  
  28629. static
  28630. NODE T2a[2] = {
  28631. { ' ', 2, NULL },
  28632. { 's', FILES, NULL }
  28633. };
  28634.  
  28635. static
  28636. NODE T29[2] = {
  28637. { ' ', 2, NULL },
  28638. { 'e', 0, T2a }
  28639. };
  28640.  
  28641. static
  28642. NODE T28[2] = {
  28643. { ' ', 2, NULL },
  28644. { '1', 0, T29 }
  28645. };
  28646.  
  28647. static
  28648.  
  28649. NODE T27[2] = {
  28650. { ' ', 2, NULL },
  28651. { 'i', 0, T28 }
  28652. };
  28653.  
  28654. static
  28655. NODE T2f[2] = {
  28656. { ' ', 2, NULL },
  28657. { 'n', HIDDEN, NULL }
  28658. };
  28659.  
  28660. static
  28661. NODE T2e[2] = {
  28662. { ' ', 2, NULL },
  28663. { 'e', 0, T2f }
  28664. };
  28665.  
  28666. static
  28667. NODE T2d[2] = {
  28668. { ' ', 2, NULL },
  28669. { 'd', 0, T2e }
  28670. };
  28671.  
  28672. static
  28673. NODE T2c[2] = {
  28674. { ' ', 2, NULL },
  28675. { 'd', 0, T2d }
  28676. };
  28677.  
  28678. static
  28679. NODE T2b[2] = {
  28680. { ' ', 2, NULL },
  28681. { 'i', 0, T2c }
  28682. };
  28683.  
  28684. static
  28685. NODE T33[2] = {
  28686. { ' ', 2, NULL },
  28687. { 'l', LABEL, NULL }
  28688. };
  28689.  
  28690. static
  28691. NODE T32[2] = {
  28692. { ' ', 2, NULL },
  28693. { 'e', 0, T33 }
  28694. };
  28695.  
  28696. static
  28697. NODE T31[2] = {
  28698. { ' ', 2, NULL },
  28699. { 'b', 0, T32 }
  28700. };
  28701.  
  28702. static
  28703. NODE T30[2] = {
  28704. { ' ', 2, NULL },
  28705. { 'a', 0, T31 }
  28706. };
  28707.  
  28708.  
  28709. static
  28710. NODE T3a[2] = {
  28711. { ' ', 2, NULL },
  28712. { 'd', MODIFIED, NULL }
  28713. };
  28714.  
  28715. static
  28716. NODE T39[2] = {
  28717. { ' ', 2, NULL },
  28718. { 'e', 0, T3a }
  28719. };
  28720.  
  28721. static
  28722. NODE T38[2] = {
  28723. { ' ', 2, NULL },
  28724. { 'i', 0, T39 }
  28725. };
  28726.  
  28727. static
  28728. NODE T37[2] = {
  28729. { ' ', 2, NULL },
  28730. { 'f', 0, T38 }
  28731. };
  28732.  
  28733. static
  28734. NODE T36[2] = {
  28735. { ' ', 2, NULL },
  28736. { 'i', 0, T37 }
  28737. };
  28738.  
  28739. static
  28740. NODE T35[2] = {
  28741. { ' ', 2, NULL },
  28742. { 'd', 0, T36 }
  28743. };
  28744.  
  28745. static
  28746. NODE T34[2] = {
  28747. { ' ', 2, NULL },
  28748. { 'o', 0, T35 }
  28749. };
  28750.  
  28751. static
  28752. NODE T3d[2] = {
  28753. { ' ', 2, NULL },
  28754. { 'e', NAME, NULL }
  28755. };
  28756.  
  28757. static
  28758. NODE T3c[2] = {
  28759. { ' ', 2, NULL },
  28760. { 'm', 0, T3d }
  28761. };
  28762.  
  28763. static
  28764. NODE T3e[2] = {
  28765. { ' ', 2, NULL },
  28766. { 't', NOT, NULL }
  28767. };
  28768.  
  28769.  
  28770. static
  28771. NODE T3b[3] = {
  28772. { ' ', 3, NULL },
  28773. { 'a', 0, T3c },
  28774. { 'o', 0, T3e }
  28775. };
  28776.  
  28777. static
  28778. NODE T3f[2] = {
  28779. { ' ', 2, NULL },
  28780. { 'r', OR, NULL },
  28781. };
  28782.  
  28783. static
  28784. NODE T43[2] = {
  28785. { ' ', 2, NULL },
  28786. { 't', PRINT, NULL }
  28787. };
  28788.  
  28789. static
  28790. NODE T42[2] = {
  28791. { ' ', 2, NULL },
  28792. { 'n', 0, T43 }
  28793. };
  28794.  
  28795. static
  28796. NODE T41[2] = {
  28797. { ' ', 2, NULL },
  28798. { 'i', 0, T42 }
  28799. };
  28800.  
  28801. static
  28802. NODE T40[2] = {
  28803. { ' ', 2, NULL },
  28804. { 'r', 0, T41 }
  28805. };
  28806.  
  28807. static
  28808. NODE T4a[2] = {
  28809. { ' ', 2, NULL },
  28810. { 'y', READONLY, NULL }
  28811. };
  28812.  
  28813. static
  28814. NODE T49[2] = {
  28815. { ' ', 2, NULL },
  28816. { 'l', 0, T4a }
  28817. };
  28818.  
  28819. static
  28820. NODE T48[2] = {
  28821. { ' ', 2, NULL },
  28822. { 'n', 0, T49 }
  28823. };
  28824.  
  28825. static
  28826. NODE T47[2] = {
  28827. { ' ', 2, NULL },
  28828.  
  28829. { 'o', 0, T48 }
  28830. };
  28831.  
  28832. static
  28833. NODE T46[2] = {
  28834. { ' ', 2, NULL },
  28835. { 'd', 0, T47 }
  28836. };
  28837.  
  28838. static
  28839. NODE T4d[2] = {
  28840. { ' ', 2, NULL },
  28841. { 's', RECURS, NULL }
  28842. };
  28843.  
  28844. static
  28845. NODE T4c[2] = {
  28846. { ' ', 2, NULL },
  28847. { 'r', 0, T4d }
  28848. };
  28849.  
  28850. static
  28851. NODE T4b[2] = {
  28852. { ' ', 2, NULL },
  28853. { 'u', 0, T4c }
  28854. };
  28855.  
  28856. static
  28857. NODE T45[3] = {
  28858. { ' ', 3, NULL },
  28859. { 'a', 0, T46 },
  28860. { 'c', 0, T4b }
  28861. };
  28862.  
  28863. static
  28864. NODE T44[2] = {
  28865. { ' ', 2, NULL },
  28866. { 'e', 0, T45 }
  28867. };
  28868.  
  28869. static
  28870. NODE T52[2] = {
  28871. { ' ', 2, NULL },
  28872. { 'h', SEARCH, NULL }
  28873. };
  28874.  
  28875. static
  28876. NODE T51[2] = {
  28877. { ' ', 2, NULL },
  28878. { 'c', 0, T52 }
  28879. };
  28880.  
  28881. static
  28882. NODE T50[2] = {
  28883. { ' ', 2, NULL },
  28884. { 'r', 0, T51 }
  28885. };
  28886.  
  28887. static
  28888.  
  28889. NODE T55[2] = {
  28890. { ' ', 2, NULL },
  28891. { 't', SELECT, NULL }
  28892. };
  28893.  
  28894. static
  28895. NODE T54[2] = {
  28896. { ' ', 2, NULL },
  28897. { 'c', 0, T55 }
  28898. };
  28899.  
  28900. static
  28901. NODE T53[2] = {
  28902. { ' ', 2, NULL },
  28903. { 'e', 0, T54 }
  28904. };
  28905.  
  28906. static
  28907. NODE T4f[3] = {
  28908. { ' ', 2, NULL },
  28909. { 'a', 0, T50 },
  28910. { 'l', 0, T53 }
  28911. };
  28912.  
  28913. static
  28914. NODE T59[2] = {
  28915. { ' ', 2, NULL },
  28916. { 'm', SYSTEM, NULL }
  28917. };
  28918.  
  28919. static
  28920. NODE T58[2] = {
  28921. { ' ', 2, NULL },
  28922. { 'e', 0, T59 }
  28923. };
  28924.  
  28925. static
  28926. NODE T57[2] = {
  28927. { ' ', 2, NULL },
  28928. { 't', 0, T58 }
  28929. };
  28930.  
  28931. static
  28932. NODE T56[2] = {
  28933. { ' ', 2, NULL },
  28934. { 's', 0, T57 }
  28935. };
  28936.  
  28937. static
  28938. NODE T4e[3] = {
  28939. { ' ', 3, NULL },
  28940. { 'e', 0, T4f },
  28941. { 'y', 0, T56 }
  28942. };
  28943.  
  28944. static
  28945. NODE T0[21] = {
  28946. { ' ', 21, NULL },
  28947. { '(', L_PAREN, NULL },
  28948.  
  28949. { ')', R_PAREN, NULL },
  28950. { ',', COMMA, NULL },
  28951. { '/', F_SLASH, NULL },
  28952. { 'a', 0, T1 },
  28953. { 'b', 0, T17 },
  28954. { 'd', 0, T1c },
  28955. { 'e', 0, T24 },
  28956. { 'f', 0, T27 },
  28957. { 'h', 0, T2b },
  28958. { '1', 0, T30 },
  28959. { 'm', 0, T34 },
  28960. { 'n', 0, T3b },
  28961. { 'o', 0, T3f },
  28962. { 'p', 0, T40 },
  28963. { 'r', 0, T44 },
  28964. { 's', 0, T4e },
  28965. { '{', L_BRACE, NULL },
  28966. { '', BAR, NULL },
  28967. { '}', R_BRACE, NULL }
  28968. };
  28969.  
  28970. /*----------------------------------------------------------
  28971.  Routine : OpenPrg() --- Open the ASCII text file
  28972.  that contains the back up program.
  28973.  
  28974.  Inputs : FileNm - File name of source file.
  28975. ----------------------------------------------------------*/
  28976.  
  28977. void OpenPrg(char *FileNm)
  28978. {
  28979. /* Open the program script file. */
  28980. if ((PrgFl = fopen(FileNm, "rt")) == NULL)
  28981. {
  28982. fprintf(stderr, "OpenPrg (fopen) : Could not open
  28983. file '%s' for "
  28984. "reading.\n", FileNm);
  28985. exit( -1 );
  28986. }
  28987.  
  28988. /* Initialize object variables. */
  28989. *word = *PrvWd = '\0';
  28990. }
  28991.  
  28992. /*----------------------------------------------------------------------
  28993.  Routine : ParsErr() --- Report a parse error.
  28994.  
  28995.  Inputs : Err - Error string.
  28996. ----------------------------------------------------------------------*/
  28997.  
  28998. void ParsErr(char *Err)
  28999. {
  29000. /* Print line number and error massage. */
  29001. fprintf(stderr, "Error in Line: %d, %s.\n", LnNo + 1, Err);
  29002.  
  29003. /* If there is a previous word, show it. */
  29004. if ( *word )
  29005. fprintf(stderr, "\t0n or after word '%s'\n", word);
  29006. exit( -1 );
  29007. }
  29008.  
  29009.  
  29010. /*---------------------------------------------------------------------
  29011.  Routine : TrieSrch() --- Search the trie for a key word.
  29012.  
  29013.  Inputs : Trie - The trie level pointer.
  29014.  ch - The current character to search for.
  29015.  WordPtr - The pointer to the current byte of the word buffer.
  29016.  Returns : Returns either a token value or
  29017.  NOT_FND - For key word not found.
  29018.  EOF - For end of file.
  29019. ----------------------------------------------------------------------*/
  29020.  
  29021. static
  29022. int TrieSrch(NODE *Trie,
  29023. int ch,
  29024. char *WordPtr)
  29025. {
  29026. register int mid; /* Mid point of array piece. */
  29027. register TKNS ret; /* Return value of comparison. */
  29028.  
  29029. auto int lo; /* Limits of current array piece for */
  29030. auto int hi; /* binary search. */
  29031.  
  29032. /* Make sure that input is lower case. */
  29033. ch = tolower( ch );
  29034.  
  29035. /* Search for a token. */
  29036. hi = Trie[0].token - 1;
  29037. lo = 1;
  29038. do
  29039. {
  29040. /* Find mid point of current array piece. */
  29041. mid = (lo + hi) >> 1;
  29042.  
  29043. /* Do character comparison. */
  29044. ret = ch - Trie[mid].c;
  29045.  
  29046. /* Fix the array limits. */
  29047. if (ret <= 0)
  29048. hi = mid - 1;
  29049. if (ret >= 0)
  29050. lo = mid + 1;
  29051.  
  29052. } while (hi >= lo);
  29053.  
  29054. /* If the character matches one of the entries in this level and this
  29055. * entry has a child, recurse. If a match is found but the matching
  29056. * entry has no child, return the token value associated with the
  29057. * match. If the return value from the recursive call indicates that
  29058. * no match was found at a lower level, return the token value
  29059. * associated with the match at this level of the trie.
  29060. */
  29061. if (ret == 0)
  29062. {
  29063. /* Save the current character in the buffer for error reporting. */
  29064. *WordPtr++ = ch;
  29065.  
  29066. /* Are we looking for more characters in this string? */
  29067. if ( Trie[mid].child )
  29068.  
  29069. {
  29070. /* Get the next character. */
  29071. if ((ch = fgetc( PrgFl )) == EOF)
  29072. return( EOF );
  29073.  
  29074. /* Search next level. */
  29075. if ((ret = TrieSrch(Trie[mid].child, ch, WordPtr)) == NOT_FND)
  29076. {
  29077. ungetc(ch, PrgFl);
  29078. return( Trie[mid].token );
  29079. }
  29080. return( ret );
  29081. }
  29082. else
  29083. {
  29084. /* Properly NUL terminate the buffer that the keyword is
  29085. * being saved in and return the token value.
  29086. */
  29087. *WordPtr = '\0';
  29088. return( Trie[mid].token );
  29089. }
  29090. }
  29091.  
  29092. /* Terminate string in keyword buffer and return not found. */
  29093. *WordPtr = '\0';
  29094. return( NOT_FND );
  29095. }
  29096.  
  29097. /*----------------------------------------------------------------------
  29098.  Routine : GetNo --- Get a number from the file.
  29099.  
  29100.  Inputs : word - Pointer to word buffer for error reporting.
  29101.  Outputs : RetNo - Returns the number read from the file.
  29102.  
  29103.  Returns : Returns the last character read from the file or EOF.
  29104. ----------------------------------------------------------------------*/
  29105.  
  29106. static
  29107. int GetNo(char **word,
  29108. long *RetNo)
  29109. {
  29110. auto int c;
  29111.  
  29112. /* Get number. */
  29113. *RetNo = OL;
  29114. while ((c = fgetc ( PrgFl )) >= '0' && c <= '9')
  29115. {
  29116. /* Save character in word buffer. */
  29117. *(*word)++ = c;
  29118.  
  29119. /* Calculate value of number. */
  29120. *RetNo = *RetNo * 10L + (long) (c - '0');
  29121. }
  29122. return( c );
  29123. }
  29124.  
  29125. /*----------------------------------------------------------------------
  29126.  Routine : Lex() --- Get the next key word from the input file.
  29127.  
  29128.  
  29129.  Outputs : sym - The symbolic data read from the file.
  29130.  
  29131.  Returns : Returns the token read or EOF.
  29132. ----------------------------------------------------------------------*/
  29133.  
  29134. TKNS Lex(TOKEN *sym)
  29135. {
  29136. register int i;
  29137. register int tkn;
  29138. auto int ch;
  29139. auto char *bf;
  29140.  
  29141. /* Strip comments and white space. If the character read is a '#',
  29142. * every thing to the end of the line is a comment.
  29143. */
  29144. ch = fgetc( PrgFl );
  29145. while (ch == ' ' ch == '\t' ch == '\n' ch == '#')
  29146. {
  29147. /* Process the special characters '#' and '\n'. */
  29148. if (ch == '\n')
  29149. /* End of line, increment the line number. */
  29150. LnNo++;
  29151. else if (ch == '#')
  29152. {
  29153. /* Found a comment character, strip all characters to end
  29154. * of line and increment the line number.
  29155. */
  29156. while (fgetc( PrgFl ) != '\n')
  29157. ;
  29158. LnNo++;
  29159. }
  29160.  
  29161. /* Get the next character. */
  29162. ch = fgetc( PrgFl );
  29163. }
  29164.  
  29165. /* Get strings, etc. */
  29166. if (ch == ' " ')
  29167. {
  29168.  
  29169. /* Get contents of string. */
  29170. bf = sym->str;
  29171. for (i = 0; i < MAX_STR; i++)
  29172. if ((ch = fgetc( PrgFl )) != ' " ' && ch != EOF)
  29173. *bf++ = ch;
  29174. else
  29175. break;
  29176. *bf = '\0';
  29177.  
  29178. /* Return string token. */
  29179. strcpy(word, sym->str);
  29180. return( STRING );
  29181. }
  29182. else if (ch >= '0' && ch <= '9')
  29183. {
  29184. auto long no;
  29185.  
  29186. /* Establish a pointer to the word buffer and unget the
  29187. * numeric character for re-reading.
  29188.  
  29189. */
  29190. bf = word;
  29191. ungetc(ch, PrgFl);
  29192.  
  29193. /* Get number, time or date. */
  29194. if ((ch = GetNo(&bf, &no)) == ':')
  29195. {
  29196. /* Getting time, not number. */
  29197. *bf++ = ch;
  29198.  
  29199. sym->ftime.ti_hour = (unsigned char) no;
  29200. sym->ftime.ti_hund = (unsigned char) 0;
  29201.  
  29202. /* Get minutes. */
  29203. if ((ch = GetNo(&bf, &no)) == ':')
  29204. {
  29205. /* Save minutes. */
  29206. *bf++ = ch;
  29207. sym->ftime.ti_min = (unsigned char) no;
  29208.  
  29209. /* Get seconds. */
  29210. if ((ch = GetNo(&bf, &no)) == '.')
  29211. {
  29212. *bf = '\0';
  29213. ParsErr( "Hundredths of seconds not allowed in "
  29214. "time expressions" );
  29215. }
  29216. sym->ftime.ti_sec = (unsigned char) no;
  29217. }
  29218. else
  29219. {
  29220. /* No seconds to get. */
  29221. sym->ftime.ti_min = (unsigned char) no;
  29222. sym->ftime.ti_sec = (unsigned char) 0;
  29223. }
  29224.  
  29225. /* This is a time. */
  29226. tkn = TIME;
  29227. }
  29228. else if (ch == '/')
  29229. {
  29230. /* Getting date, not number. */
  29231. *bf++ = ch;
  29232. sym->fdate.da_mon = (char) no;
  29233.  
  29234. /* Get day. */
  29235. if ((ch = GetNo(&bf, &no)) == '/')
  29236. {
  29237. /* Save character. */
  29238. *bf++ = ch;
  29239. sym->fdate.da_day = (char) no;
  29240.  
  29241. /* Get year. */
  29242. ch = GetNo(&bf, &no);
  29243. if (no > 1980L)
  29244. no -= 1980L;
  29245. else if (no > 80L && no < 100L)
  29246. no -= 80L;
  29247. else
  29248.  
  29249. {
  29250. *bf = '\0';
  29251. ParsErr( "Error, bad year value in date expression." );
  29252. }
  29253. sym->fdate.da_year = (int) no;
  29254. }
  29255. else
  29256. {
  29257. *bf = '\0';
  29258. ParsErr( "Missing year in date expression" );
  29259. }
  29260.  
  29261. /* This is a date. */
  29262. tkn = DATE;
  29263. }
  29264. else
  29265. {
  29266. /* Just an integer constant. */
  29267. sym->no = no;
  29268. tkn = NUMBER;
  29269. }
  29270.  
  29271. /* Return the unused character. */
  29272. *bf = '\0';
  29273. ungetc(ch, PrgFl);
  29274. return( tkn );
  29275. }
  29276. else if (ch == EOF)
  29277. return( EOF );
  29278.  
  29279. /* Call the trie search routine to return the next token, EOF
  29280. * or NOT_FND. If not found, print an error and quit.
  29281. */
  29282. if ((tkn = TrieSrch(TO, ch, word)) == NOT_FND)
  29283. {
  29284. /* Illegal first character in word. */
  29285. if ( *PrvWd )
  29286. fprintf(stderr, "Parse - Error in Line: %d, cannot identify "
  29287. "word after '%s'.\n", LnNo + 1, PrvWd);
  29288. else
  29289. fprintf(stderr, "Parse - Error in Line: %d, cannot identify "
  29290. "first word in file.\n", LnNo + 1);
  29291. exit( -1 );
  29292. }
  29293. else if (tkn == 0)
  29294. {
  29295. /* Illegal word. */
  29296. fprintf(stderr, "Parse - Error in Line: %d, cannot identify "
  29297. "word '%s'.\n", LnNo + 1, word);
  29298. exit( -1 );
  29299. }
  29300. strcpy(PrvWd, word);
  29301.  
  29302. /* Return the token found. */
  29303. return( tkn );
  29304. }
  29305.  
  29306. /* End of File */
  29307.  
  29308.  
  29309.  
  29310.  
  29311.  
  29312.  
  29313.  
  29314.  
  29315.  
  29316.  
  29317.  
  29318.  
  29319.  
  29320.  
  29321.  
  29322.  
  29323.  
  29324.  
  29325.  
  29326.  
  29327.  
  29328.  
  29329.  
  29330.  
  29331.  
  29332.  
  29333.  
  29334.  
  29335.  
  29336.  
  29337.  
  29338.  
  29339.  
  29340.  
  29341.  
  29342.  
  29343.  
  29344.  
  29345.  
  29346.  
  29347.  
  29348.  
  29349.  
  29350.  
  29351.  
  29352.  
  29353.  
  29354.  
  29355.  
  29356.  
  29357.  
  29358.  
  29359.  
  29360.  
  29361.  
  29362.  
  29363.  
  29364.  
  29365.  
  29366.  
  29367.  
  29368.  
  29369.  
  29370.  
  29371.  
  29372. Standard C
  29373.  
  29374.  
  29375. The Header <stdlib.h>
  29376.  
  29377.  
  29378.  
  29379.  
  29380. P.J. Plauger
  29381.  
  29382.  
  29383. P.J. Plauger is senior editor of The C Users Journal. He is secretary of the
  29384. ANSI C standards committee, X3J11, and convenor of the ISO C standards
  29385. committee, WG14. His latest book is The Standard C Library, published by
  29386. Prentice-Hall. You can reach him care of The C Users Journal or via Internet
  29387. at pjp@plauger.uunet.uu.net.
  29388.  
  29389.  
  29390.  
  29391.  
  29392. Introduction
  29393.  
  29394.  
  29395. The header <stdlib.h> is a hodgepodge. Committee X3J11 invented this header as
  29396. a place to define macros and declare functions that had no other sensible
  29397. home:
  29398. Many existing functions, such as abs and malloc, had no traditional headers to
  29399. declare them. X3J11 felt strongly that every functions should be declared in a
  29400. standard header. If such a function seemed out of place in all other headers,
  29401. it ended up declared in <stdlib.h>.
  29402. New groups of macros and functions ended up in new standard headers wherever
  29403. possible. <float.h> and <locale.h> are clear examples. Additions to existing
  29404. groups ended up in existing headers. strcoll, declared in <string.h>, and
  29405. strftime, declared in <time.h>, are also fairly clear. Other macros and
  29406. functions are harder to categorize. These ended up defined or declared in
  29407. <stdlib.h>.
  29408. This header is not the only hodgepodge. I discuss the evolution of the header
  29409. <stddef.h> in Standard C, CUJ December 1991.
  29410. To provide some structure, I organize the functions into six groups:
  29411. integer math (abs, div, labs, and ldiv) -- performing simple integer
  29412. arithmetic
  29413. algorithms (bsearch, qsort, rand, and srand) -- capturing operations complex
  29414. and widespread enough to warrant packaging as library functions
  29415. text conversions (atof, atoi, atol, strtod, strtol, and strtoul) --
  29416. determining encoded arithmetic values from text representations
  29417. multibyte conversions (mblen, mbstowcs, mbtowc, wcstombs, and wctomb) mapping
  29418. between multi-byte and wide-character encodings
  29419. storage allocation (calloc, free, malloc, and realloc) -- managing a heap of
  29420. data objects
  29421. environmental interactions (abort, atexit, exit, getenv, and system) --
  29422. interfacing between the program and the execution environment
  29423. I discuss separately how to implement the functions in each of these groups.
  29424. This month, I cover only the first two groups. I won't bother to present the
  29425. header as a whole. It's pretty straightforward.
  29426.  
  29427.  
  29428. The C Standard on Integer Math
  29429.  
  29430.  
  29431.  
  29432.  
  29433. 7.10.6 Integer arithmetic functions
  29434.  
  29435.  
  29436.  
  29437.  
  29438. 7.10.6.1 The abs function
  29439.  
  29440.  
  29441.  
  29442.  
  29443. Synopsis
  29444.  
  29445.  
  29446. #include <stdlib.h>
  29447. int abs(int j);
  29448.  
  29449.  
  29450. Description
  29451.  
  29452.  
  29453.  
  29454. The abs function computes the absolute value of an integer j. If the result
  29455. cannot be represented, the behavior is undefined.130 [FN130. The absolute
  29456. value of the most negative number cannot be represented in two's complement.]
  29457.  
  29458.  
  29459. Returns
  29460.  
  29461.  
  29462. The abs function returns the absolute value.
  29463.  
  29464.  
  29465. 7.10.6.2 The div function
  29466.  
  29467.  
  29468.  
  29469.  
  29470. Synopsis
  29471.  
  29472.  
  29473. #include <stdlib.h>
  29474. div_t div(int numer, int denom);
  29475.  
  29476.  
  29477. Description
  29478.  
  29479.  
  29480. The div function computes the quotient and remainder of the division of the
  29481. numerator numer by the denominator denom. If the division is inexact, the
  29482. resulting quotient is the integer of lesser magnitude that is the nearest to
  29483. the algebraic quotient. If the result cannot be represented, the behavior is
  29484. undefined; otherwise, quot * denom + rem shall equal numer.
  29485.  
  29486.  
  29487. Returns
  29488.  
  29489.  
  29490. The div function returns a structure of type div_t, comprising both the
  29491. quotient and the remainder. The structure shall contain the following members,
  29492. in either order:
  29493. int quot; /* quotient */
  29494. int rem; /* remainder */
  29495.  
  29496.  
  29497. 7.10.6.3 The labs function
  29498.  
  29499.  
  29500.  
  29501.  
  29502. Synopsis
  29503.  
  29504.  
  29505. #include <stdlib.h>
  29506. long int labs(long int j);
  29507.  
  29508.  
  29509. Description
  29510.  
  29511.  
  29512. The labs function is similar to the abs function, except that the argument and
  29513. the returned value each have type long int.
  29514.  
  29515.  
  29516. 7.10.6.4 The ldiv function
  29517.  
  29518.  
  29519.  
  29520.  
  29521.  
  29522. Synopsis
  29523.  
  29524.  
  29525. #include <stdlib.h>
  29526. ldiv_t ldiv(long int numer, long int denom);
  29527.  
  29528.  
  29529. Description
  29530.  
  29531.  
  29532. The ldiv function is similar to the div function, except that the arguments
  29533. and the members of the returned structure (which has type ldiv_t) all have
  29534. type long int.
  29535.  
  29536.  
  29537. Using the Integer Arithmetic Functions
  29538.  
  29539.  
  29540. Here is a brief summary of the four arithmetic functions: abs -- Call abs(x)
  29541. instead of writing the idiom x < 0 ? -x : x. A growing number of Standard C
  29542. translators generate inline code for abs that is smaller and faster than the
  29543. idiom. In addition, you avoid the occasional surprise when you inadvertently
  29544. evaluate twice an expression with side effects. Note that on a
  29545. two's-complement machine, abs can generate an overflow.
  29546. div -- You call div for one of two reasons:
  29547. div always computes a quotient that truncates toward zero, along with the
  29548. corresponding remainder, regardless of how the operators / and % behave in a
  29549. given implementation. This can be important when one of the operands is
  29550. negative. The expression (-3)/2 can yield either -2 or -1, while div(-3,
  29551. 2).quot always yields -1. Similarly, (-3)%2 can yield either 1 or -1, while
  29552. div(-3, 2).rem always yields -1.
  29553. div computes both the quotient and remainder at the same time. That can be
  29554. handy when you need both results. It might even be more efficient if the
  29555. function expands to inline code that contains only a single divide.
  29556. Note that the members of the resulting structure type div_t can occur in
  29557. either order. Don't make any assumptions about the representation of this
  29558. structure.
  29559. labs -- is the long version of abs.
  29560. ldiv -- is the long version of div.
  29561.  
  29562.  
  29563. Implementing the Arithmetic Functions
  29564.  
  29565.  
  29566. Listing 1 shows the file abs.c. The absolute value function abs is the
  29567. simplest of the integer math functions. You cannot provide a masking macro,
  29568. however, because you have to access the value of the argument twice. Some
  29569. computer architectures have special instructions for computing the absolute
  29570. value. That makes abs a prime candidate for special treatment as a built-in
  29571. function generating inline code.
  29572. Listing 2 shows the file div.c. It provides a portable implementation of the
  29573. div function. You can eliminate the test if you know that negative quotients
  29574. truncate toward zero. Most computer architectures have a divide instruction
  29575. that develops both quotient and remainder at the same time. Those that develop
  29576. proper negative quotients are also candidates for built-in functions. An
  29577. implementation is at liberty to reorder the members of the structure type
  29578. div_t to match what the hardware generates.
  29579. Listing 3 shows the file labs.c and Listing 4 shows the file ldiv.c. Both
  29580. define functions that are simply long versions of abs and div.
  29581.  
  29582.  
  29583. The C Standard on the Algorithmic Functions
  29584.  
  29585.  
  29586.  
  29587.  
  29588. 7.10.2 Pseudo-random sequence generation functions
  29589.  
  29590.  
  29591.  
  29592.  
  29593. 7.10.2.1 The rand function
  29594.  
  29595.  
  29596.  
  29597.  
  29598. Synopsis
  29599.  
  29600.  
  29601. #include <stdlib.h>
  29602. int rand(void);
  29603.  
  29604.  
  29605. Description
  29606.  
  29607.  
  29608. The rand function computes a sequence of pseudo-random integers in the range 0
  29609. to RAND_MAX.
  29610.  
  29611. The implementation shall behave as if no library function calls the rand
  29612. function.
  29613.  
  29614.  
  29615. Returns
  29616.  
  29617.  
  29618. The rand function returns a pseudo-random integer.
  29619.  
  29620.  
  29621. Environmental Limit
  29622.  
  29623.  
  29624. The value of the RAND_MAX macro shall be at least 32767.
  29625.  
  29626.  
  29627. 7.10.2.2 The srand function
  29628.  
  29629.  
  29630.  
  29631.  
  29632. Synopsis
  29633.  
  29634.  
  29635. #include <stdlib.h>
  29636. void srand(unsigned int seed);
  29637.  
  29638.  
  29639. Description
  29640.  
  29641.  
  29642. The srand function uses the argument as a seed for a new sequence of
  29643. pseudo-random numbers to be returned by subsequent calls to rand. If srand is
  29644. then called with the same seed value, the sequence of pseudo-random numbers
  29645. shall be repeated. If rand is called before any calls to srand have been made,
  29646. the same sequence shall be generated as when srand is first called with a seed
  29647. value of 1.
  29648. The implementation shall behave as if no library function calls the srand
  29649. function.
  29650.  
  29651.  
  29652. Returns
  29653.  
  29654.  
  29655. The srand function returns no value.
  29656.  
  29657.  
  29658. Example
  29659.  
  29660.  
  29661. The following functions define a portable implementation of rand and srand.
  29662. static unsigned long int next = 1;
  29663. int rand(void) /* RAND_MAX assumed to be 32767 */
  29664. {
  29665. next = next * 1103515245 + 12345;
  29666. return (unsigned int)
  29667. next/65536) % 32768;
  29668. }
  29669. void srand(unsigned int seed)
  29670. {
  29671. next = seed;
  29672. }
  29673.  
  29674.  
  29675. 7.10.5 Searching and sorting utilities
  29676.  
  29677.  
  29678.  
  29679.  
  29680.  
  29681. 7.10.5.1 The bsearch function
  29682.  
  29683.  
  29684.  
  29685.  
  29686. Synopsis
  29687.  
  29688.  
  29689. #include <stdlib.h>
  29690. void *bsearch(const void *key,
  29691. const void *base, size_t nmemb,
  29692. size_t size, int (*compar
  29693. const void *, const void *));
  29694.  
  29695.  
  29696. Description
  29697.  
  29698.  
  29699. The bsearch function searches an array of nmemb objects, the initial element
  29700. of which is pointed to by base, for an element that matches the object pointed
  29701. to by key. The size of each element of the array is specified by size.
  29702. The comparison function pointed to by compar is called with two arguments that
  29703. point to the key object and to an array element, in that order. The function
  29704. shall return an integer less than, equal to, or greater than zero if the key
  29705. object is considered, respectively, to be less than, to match, or to be
  29706. greater than the array element. The array shall consist of: all the elements
  29707. that compare less than, all the elements that compare equal to, and all the
  29708. elements that compare greater than the key object, in that order.129 [FN129.
  29709. In practice, the entire array is sorted according to the comparison function.]
  29710.  
  29711.  
  29712. Returns
  29713.  
  29714.  
  29715. The bsearch function returns a pointer to a matching element of the array, or
  29716. a null pointer if no match is found. If two elements compare as equal, which
  29717. element is matched is unspecified.
  29718.  
  29719.  
  29720. 7.10.5.2 The qsort function
  29721.  
  29722.  
  29723.  
  29724.  
  29725. Synopsis
  29726.  
  29727.  
  29728. #include <stdlib.h>
  29729. void qsort(void *base, size_t nmemb, size_t size,
  29730. int (*compar)(const void *, const void *));
  29731.  
  29732.  
  29733. Description
  29734.  
  29735.  
  29736. The qsort function sorts an array of nmemb objects, the initial element of
  29737. which is pointed to by base. The size of each object is specified by size.
  29738. The contents of the array are sorted into ascending order according to a
  29739. comparison function pointed to by compar, which is called with two arguments
  29740. that point to the objects being compared. The function shall return an integer
  29741. less than, equal to, or greater than zero if the first argument is considered
  29742. to be respectively less than, equal to, or greater than the second.
  29743. If two elements compare as equal, their order in the sorted array is
  29744. unspecified.
  29745.  
  29746.  
  29747. Returns
  29748.  
  29749.  
  29750. The qsort function returns no value.
  29751.  
  29752.  
  29753. Using the Algorithmic Functions
  29754.  
  29755.  
  29756.  
  29757. Here is a brief summary of the four algorithmtic functions:
  29758. bsearch -- Use this function to search any array whose elements are ordered by
  29759. pairwise comparisons. You define the ordering with a comparison function that
  29760. you provide. For example, you can build a keyword lookup function from the
  29761. basic form as shown in Listing 5.
  29762. A few caveats:
  29763. If a key compares equal to two or more elements, bsearch can return a pointer
  29764. to any of these elements.
  29765. Beware of changes in how elements sort when the execution character set
  29766. changes -- call qsort, described below, with a compatible comparison function
  29767. to ensure that an array is properly ordered.
  29768. Be careful using the functions strcmp or strcoll, declared in <string.h>,
  29769. directly. Both require that strings be stored in the array to be searched. You
  29770. cannot use them to search an array of pointers to strings. To use strcmp, for
  29771. example, you must write a function pointer argument that looks like (int
  29772. (*)(const void *, const void *))&strcmp.
  29773. qsort -- Use this function to sort any array whose elements are ordered by
  29774. pairwise comparisons. You define the ordering with a comparison function that
  29775. you provide. The comparison function has a specification similar to that for
  29776. the function bsearch, described above. Note, however, that the bsearch
  29777. comparison function compares a key to an array element. The sort comparison
  29778. function compares two array elements.
  29779. A few caveats:
  29780. Don't assume that the function uses the "Quicksort" algorithm, despite the
  29781. name. It may not. If two or more elements compare equal, qsort can leave these
  29782. elements in any relative order. Hence, qsort is not a stable sort.
  29783. Beware of changes in how elements sort when the execution character set
  29784. changes.
  29785. Be careful using the functions strcmp or strcoll declared in <string.h>,
  29786. directly. Both require that strings be stored in the array to be sorted. You
  29787. cannot use them to sort an array of pointers to strings. To use strcmp, for
  29788. example, you must write a function pointer argument that looks like (int
  29789. (*)(const void *, const void *))&strcmp.
  29790. rand -- Call rand to obtain the next value in a pseudo-random sequence. You
  29791. get exactly the same sequence following each call to srand with a given
  29792. argument value. That is often desirable behavior, particularly when you are
  29793. debugging a program. If you want less predictable behavior, call clock or
  29794. time, declared in <time.h> to obtain an argument for srand. The behavior of
  29795. rand can vary among implementations. If you want exactly the same
  29796. pseudo-random sequence at all times, copy the version presented here.
  29797. Use RAND_MAX to scale values returned from rand. For example, if you want
  29798. random numbers of type float distributed over the interval [0.0, 1.0], write
  29799. the expression (float)rand()/RAND_MAX. The value of RAND_MAX is at least
  29800. 32,767.
  29801. srand -- See rand above. The program effectively calls srand(1) at program
  29802. startup.
  29803.  
  29804.  
  29805. Implementing the Algorithmic Functions
  29806.  
  29807.  
  29808. Listing 6 shows the file qsort.c. It defines the related function qsort that
  29809. sorts an array beginning at base. I introduced the type _Cmpfun just to
  29810. simplify the declaration of arguments for the functions bsearch and qsort.
  29811. Don't use this declaration in code that you write if you want it to be
  29812. portable to other implementations.
  29813. This logic is much less simple and more debatable. It is based on the
  29814. Quicksort algorithm first developed by C.A.R. Hoare. That requires you to pick
  29815. a partition element, then partially sort the array about this partition. You
  29816. can then sort each of the two partitions by recursive application of the same
  29817. technique. The algorithm can sort quite rapidly. It can also sort very slowly.
  29818. How best to choose the pivot element is the debatable issue. Pick the first
  29819. element and an array already in sort eats a lot of time. Pick the last element
  29820. and an array in reverse sort eats a lot of time. Work too hard at picking an
  29821. element and all arrays eat a lot of time. I chose simply to pick the last
  29822. element. That favors arrays that need little rearranging. You may have reason
  29823. to choose another approach.
  29824. qsort calls itself to sort the smaller of the two partitions. It loops
  29825. internally to sort the larger of the two. That minimizes demands on dynamic
  29826. storage. At worst, each recursive call must sort an array half as big as the
  29827. earlier call. To sort N elements requires recursion no deeper than log2(N)
  29828. calls. (You can sort 1,000,000 elements with at most 20 recursive calls.)
  29829. Listing 7 shows the file bsearch.c. The function bsearch performs a binary
  29830. search on the sorted array beginning at base. The logic is simple but easy to
  29831. get wrong.
  29832. Listing 8 shows the file rand.c. The function rand generates a pseudo-random
  29833. sequence using the algorithm suggested in the C Standard. That has reasonable
  29834. properties, plus the advantage of being widely used. One virtue of a random
  29835. number generator is randomness. Another virtue, ironically, is
  29836. reproducibility. You often need to check that a calculation based on
  29837. pseudo-random numbers does what you expect. The arithmetic is performed using
  29838. unsigned long integers to avoid overflows.
  29839. Listing 9 shows the file srand.c. The function srand simply sets _Randseed,
  29840. the seed for the pseudo-random sequence generated by rand. I provide a masking
  29841. macro for srand. Hence, the header <stdlib.h> declares _Randseed, defined in
  29842. rand.c.
  29843. References
  29844. Donald Knuth, The Art of Computer Programming, Vols. 1-3 (Reading, Mass.:
  29845. Addison-Wesley, 1967 and later). Here is a rich source of algorithms, complete
  29846. with analysis and tutorial introductions. Volume 1 is Fundamental Algorithms,
  29847. volume 2 is Seminumerical Algorithms, and volume 3 is Sorting and Searching.
  29848. Some are in second edition.
  29849. You will find oodles of information on:
  29850. maintaining a heap
  29851. computing random numbers
  29852. searching ordered sequences
  29853. sorting
  29854. converting between different numeric bases
  29855. Before you tinker with the code presented here, see what Knuth has to say.
  29856. This article is excerpted in part from P.J. Plauger, The Standard C Library,
  29857. (Englewood Cliffs, N.J.: Prentice-Hall, 1992).
  29858.  
  29859. Listing 1 (abs.c)
  29860. /* abs function */
  29861. #include <stdlib.h>
  29862.  
  29863. int (abs)(int i)
  29864. { /* compute absolute value of int argument */
  29865. return ((i < 0) ? -i : i);
  29866. }
  29867.  
  29868. /* End of File */
  29869.  
  29870.  
  29871. Listing 2 (div.c)
  29872. /* div function */
  29873. #include <stdlib.h>
  29874.  
  29875. div_t (div)(int numer, int denom)
  29876. { /* compute int quotient and remainder */
  29877. div_t val;
  29878.  
  29879.  
  29880. val.quot = numer / denom;
  29881. val.rem = numer - denom * val.quot;
  29882. if (val.quot < 0 && 0 < val.rem)
  29883. { /* fix remainder with wrong sign */
  29884. val.quot += 1;
  29885. val.rem -= denom;
  29886. }
  29887. return (val);
  29888. }
  29889.  
  29890. /* End of File */
  29891.  
  29892.  
  29893. Listing 3 (labs.c)
  29894. /* labs function */
  29895. #include <stdlib.h>
  29896.  
  29897. long (labs)(long i)
  29898. { /* compute absolute value of long argument */
  29899. return ((i < 0) ? -i : i);
  29900. }
  29901.  
  29902. /* End of File */
  29903.  
  29904.  
  29905. Listing 4 (ldiv.c)
  29906. /* ldiv function */
  29907. #include <stdlib.h>
  29908.  
  29909. ldiv_t (ldiv)(long numer, long denom)
  29910. { /* compute long quotient and remainder */
  29911. ldiv_t val;
  29912.  
  29913. val.quot = numer / denom;
  29914. val.rem = numer - denom * val.quot;
  29915. if (val.quot < 0 && 0 < val.rem)
  29916. { /* fix remainder with wrong sign */
  29917. val.quot += 1;
  29918. val.rem -= denom;
  29919. }
  29920. return (val);
  29921. }
  29922.  
  29923. /* End of File */
  29924.  
  29925.  
  29926. Listing 5
  29927. #include <stdlib.h>
  29928. #include <string.h>
  29929.  
  29930. typedef enum {FLOAT, INTEGER} Code;
  29931. typedef struct {
  29932. char *s;
  29933. Code code;
  29934. } Entry;
  29935. Entry symtab[] = {
  29936. {"float", FLOAT},
  29937. {"integer", INTEGER}}
  29938.  
  29939.  
  29940. static int cmp(const void *ck, const void *ce)
  29941. { /* compare key to table element */
  29942. return (strcmp((const char *)ck, ((Entry *)ce)->s));
  29943. }
  29944.  
  29945. Entry *lookup(char *key)
  29946. { /* lookup key in table */
  29947. return (bsearch(key, symtab,
  29948. sizeof symtab / sizeof symtab[0],
  29949. sizeof symtab[0], &cmp));
  29950. }
  29951. /* End of File */
  29952.  
  29953.  
  29954. Listing 6 (qsort.c)
  29955. /* qsort function */
  29956. #include <stdlib.h>
  29957. #include <string.h>
  29958.  
  29959. /* macros */
  29960. #define MAX_BUF256 /* chunk to copy on swap */
  29961.  
  29962. void (qsort)(void *base, size_t n, size_t size, _Cmpfun *cmp)
  29963. { /* sort (char base[size])[n] using quicksort */
  29964. while (1 < n)
  29965. { /* worth sorting */
  29966. size_t i = 0;
  29967. size_t j = n - 1;
  29968. char *qi = (char *)base;
  29969. char *qj = qi + size * j;
  29970. char *qp = qj;
  29971.  
  29972. while (i < j)
  29973. { /* partition about pivot */
  29974. while (i < j && (*cmp)(qi, qp) <= 0)
  29975. ++i, qi += size;
  29976. while (i < j && (*cmp)(qp, qj) <= 0)
  29977. --j, qj -= size;
  29978. if (i < j)
  29979. { /* swap elements i and j */
  29980. char buf[MAX_BUF];
  29981. char *q1 = qi;
  29982. char *q2 = qj;
  29983. size_t m, ms;
  29984.  
  29985. for (ms = size; 0 < ms;
  29986. ms -= m, q1 += m, q2 -= m)
  29987. { /* swap as many as possible */
  29988. m = ms < sizeof (bur) ? ms : sizeof (buf);
  29989. memcpy(buf, q1, m);
  29990. memcpy(q1, q2, m);
  29991. memcpy(q2, buf, m);
  29992. }
  29993. ++i, qi += size;
  29994. }
  29995. }
  29996. if (qi != qp)
  29997. { /* swap elements i and pivot */
  29998. char buf[MAX_BUF];
  29999.  
  30000. char *q1 = qi;
  30001. char *q2 = qp;
  30002. size_t m, ms;
  30003.  
  30004. for (ms = size; 0 < ms; ms -= m, q1 += m, q2 -= m)
  30005. { /* swap as many as possible */
  30006. m = ms < sizeof (buf) ? ms : sizeof (buf);
  30007. memcpy(buf, q1, m);
  30008. memcpy(q1, q2, m);
  30009. memcpy(q2, buf, m);
  30010. }
  30011. }
  30012. j = n - i - 1, qi += size;
  30013. if (j < i)
  30014. { /* recurse on smaller partition */
  30015. if (1 < j)
  30016. qsort(qi, j, size, cmp);
  30017. n = i;
  30018. }
  30019. else
  30020. { /* lower partition is smaller */
  30021. if (1 < i)
  30022. qsort(base, i, size, cmp);
  30023. base = qi;
  30024. n = j;
  30025. }
  30026. }
  30027. }
  30028.  
  30029. /* End of File */
  30030.  
  30031.  
  30032. Listing 7 (bsearch.c)
  30033. /* bsearch function */
  30034. #include <stdlib.h>
  30035.  
  30036. void *(bsearch)(const void *key, const void *base,
  30037. size_t nelem, size_t size, _Cmpfun *cmp)
  30038. { /* search sorted table by binary chop */
  30039. const char *p;
  30040. size_t n;
  30041.  
  30042. for (p = (const char *)base, n = nelem; 0 < n; )
  30043. { /* check midpoint of whatever is left */
  30044. const size_t pivot = n > 1;
  30045. const char *const q = p + size * pivot;
  30046. const int val = (*cmp)(key, q);
  30047.  
  30048. if (val 0)
  30049. n = pivot; /* search below pivot */
  30050. else if (val == 0)
  30051. return ((void *)q); /* found */
  30052. else
  30053. { /* search above pivot */
  30054. p = q + size;
  30055. n -= pivot + 1;
  30056. }
  30057. }
  30058. return (NULL);/* no match */
  30059.  
  30060. }
  30061.  
  30062. /* End of File */
  30063.  
  30064.  
  30065. Listing 8 (rand.c)
  30066. /* rand function */
  30067. #include <stdlib.h>
  30068.  
  30069. /* the seed */
  30070. unsigned long_Randseed = 1;
  30071.  
  30072. int (rand)(void)
  30073. { /* compute pseudo-random value */
  30074. _Randseed = _Randseed * 1103515245 + 12345;
  30075. return ((unsigned int)(_Randseed >> 16) & RAND_MAX);
  30076. }
  30077.  
  30078. /* End of File */
  30079.  
  30080.  
  30081. Listing 9 (srand.c)
  30082. /* srand function */ #include <stdlib.h>
  30083.  
  30084. void (srand)(unsigned int seed)
  30085. { /* alter the seed */
  30086. _Randseed = seed;
  30087. }
  30088.  
  30089. /* End of File */
  30090.  
  30091.  
  30092.  
  30093.  
  30094.  
  30095.  
  30096.  
  30097.  
  30098.  
  30099.  
  30100.  
  30101.  
  30102.  
  30103.  
  30104.  
  30105.  
  30106.  
  30107.  
  30108.  
  30109.  
  30110.  
  30111.  
  30112.  
  30113.  
  30114.  
  30115.  
  30116.  
  30117.  
  30118.  
  30119.  
  30120.  
  30121.  
  30122.  
  30123. Doctor C's Pointers(R)
  30124.  
  30125.  
  30126. Data Structures, Part 11: Yet More on Stacks
  30127.  
  30128.  
  30129.  
  30130.  
  30131. Rex Jaeschke
  30132.  
  30133.  
  30134. Rex Jaeschke is an independent computer consultant, author, and seminar
  30135. leader. He participates in both ANSI and ISO C Standards meetings and is the
  30136. editor of The Journal of C Language Translation, a quarterly publication aimed
  30137. at implementors of C language translation tools. Readers are encouraged to
  30138. submit column topics and suggestions to Rex at 2051 Swans Neck Way, Reston, VA
  30139. 22091 or via UUCP at rex@aussie.com.
  30140.  
  30141.  
  30142.  
  30143.  
  30144. Handling Multiple Stacks of the Same Type
  30145.  
  30146.  
  30147. Last month's column described how to implement a stack and to use push and pop
  30148. functions on it. However, it would be a waste to have a different set of push
  30149. and pop functions for each stack. This month I will discuss how to share the
  30150. code.
  30151. Consider Listing 1. An object of type stack contains the current context of a
  30152. given stack. This context includes the stack's name (for debugging or
  30153. trace-back purposes), the base address of the stack, the stack's size and its
  30154. current stack pointer. The stack descriptors stack1, stack2, and stack3
  30155. therefore describe the three different int stacks. The first stack is stored
  30156. in a global array, the second in a file scope static array, while the third is
  30157. allocated at runtime using malloc. In short, the stack management functions
  30158. don't know and don't care where the stacks reside.
  30159. Listing 2 tells push and pop which stack to use but the notation is not
  30160. particularly unwieldy. Listing 3 could be made a little bit cleaner by passing
  30161. the stack descriptor by value, but that could be just a little more expensive
  30162. since the descriptor is a structure. The output produced is shown in Figure 1.
  30163.  
  30164.  
  30165. Handling Multiple Stacks of Different Types
  30166.  
  30167.  
  30168. Can this idea be extended to manage stacks of different types? The answer is a
  30169. qualified yes. One way could be to use generic pointers, as shown in Listing
  30170. 4. Here, a void pointer is used to hold the stack's base address. This also
  30171. requires an extra member to indicate the type of elements in any given stack,
  30172. as in
  30173. void push(stack *, void *);
  30174. void *pop(stack *);
  30175. The interface to push and pop gets very messy, however. Since an object of
  30176. arbitrary type cannot be passed by value, it must be passed by assigning it to
  30177. a named object and then passing that object by address, as shown in Listing 5.
  30178. (This eliminates the ability to push a constant directly.) This is possible
  30179. since all data pointer types are compatible with void *. Similarly, an
  30180. arbitrary typed value can't be returned, so the address is returned instead.
  30181. To use the value returned by pop you must use an explicit cast as well as a
  30182. dereference since pop returns a pointer (see Listing 6). In fact, since pop
  30183. returns the address of the object just popped from the stack, if you don't
  30184. dereference the pointer returned immediately, the location it points to will
  30185. be overwritten by the very next push and the value popped will change.
  30186. The source for push and pop is far from pretty. Since it cannot perform
  30187. arithmetic (via subscripting) on void pointers, you must explicitly provide
  30188. the scaling factor.
  30189.  
  30190.  
  30191. Using Unions
  30192.  
  30193.  
  30194. Another way to look at the problem is rather than have stacks of different
  30195. types, have one stack that can handle objects of different types. You can
  30196. achieve this via a union, as shown in Listing 7.
  30197. Each entry is a union of all the possible types along with a flag member that
  30198. indicates which type this entry currently represents. We push nodes by value
  30199. and return them by value using simple and obvious notation. If you try to pop
  30200. from an empty stack, instead of complaining, pop returns a copy of a local
  30201. node that has a special type field value of Error.
  30202. Next, consider Listing 8. Note the interesting controlling expression in the
  30203. while loop -- it uses the comma operator in an effective manner.
  30204. The stack management functions presented in Listing 9 are quite
  30205. straightforward.
  30206.  
  30207.  
  30208. Opposing Stacks
  30209.  
  30210.  
  30211. A stack grows and shrinks from one end only so it is possible to have two
  30212. stacks based at opposite ends of an array with their tops growing toward each
  30213. other. This can save space if both stacks don't grow very large at the same
  30214. time. However, when one is smaller, the other can grow larger and vice-versa.
  30215. The dump_stack function in Listing 10 allows us to see the contents of the
  30216. underlying array as the two stacks grow and shrink. Note that it pops in a
  30217. different order than it pushes, so the operations are staggered.
  30218. The stack in Listing 11 can only handle four elements. This helps you monitor
  30219. the progress as elements are pushed and popped. Clearly, it would be larger in
  30220. a real application. Of course, the bases of the two stacks are at either end
  30221. of the underlying array and in one stack the stack pointer increments as we
  30222. push and in the other it decrements. Stack overflow is detected when the two
  30223. stack pointers bump into each other. Note that it's OK if both of them point
  30224. to the same element since that last free element is available to which ever
  30225. stack can use it first.
  30226. The two versions of push and pop in Listing 12 are different enough that it is
  30227. not obvious you can write a single version that is both elegant and efficient.
  30228. The output produced is shown in Figure 2.
  30229. Note that even after a value is popped from a stack it still remains there --
  30230. only the stack pointer is adjusted. This is exactly what happens when a C
  30231. function returns; the values of it's automatic variables are still on the
  30232. stack but are outside the bounds of the newly adjusted stack pointer. They
  30233. remain intact until that part of the stack is overwritten for some other
  30234. purpose.
  30235. Figure 1
  30236. Stack stack1 is full
  30237. stk1: 10
  30238. stk2: 15
  30239. Stk3: 20
  30240.  
  30241. Stack stack3 is empty
  30242. stk3: 0
  30243. Figure 2
  30244. Stack contains: 0 0 0 0 sp1 = 0, sp2 = 3
  30245. Stack contains: 10 0 0 0 sp1 = 1, sp2 = 3
  30246. Stack contains: 10 0 0 12 sp1 = 1, sp2 = 2
  30247. Stack contains: 10 15 0 12 sp1 = 2, sp2 = 2
  30248. Stack contains: 10 15 34 12 sp1 = 2, sp2 = 1
  30249. Stack 1 is full
  30250. Stack contains: 10 15 34 12 sp1 = 2, sp2 = 1
  30251. Stack 2 is full
  30252. Stack contains: 10 15 34 12 sp1 = 2, sp2 = 1
  30253. stack 1: 15
  30254. Stack contains: 10 15 34 12 sp1 = 1, sp2 = 1
  30255. stack 1: 10
  30256. Stack contains: 10 15 34 12 sp1 = 0, sp2 = 1
  30257. Stack 1 is empty
  30258. stack 1: 0
  30259. Stack contains: 10 15 34 12 sp1 = 0, sp2 = 1
  30260. stack 2: 34
  30261. Stack contains: 10 15 34 12 sp1 = 0, sp2 = 2
  30262. stack 2: 12
  30263. Stack contains: 10 15 34 12 sp1 = 0, sp2 = 3
  30264. Stack 2 is empty
  30265. stack 2: 0
  30266. Stack contains: 10 15 34 12 sp1 = 0, sp2 = 3
  30267.  
  30268. Listing 1
  30269. #include <stdio.h>
  30270. #include <stdlib.h>
  30271.  
  30272. #define NUMELEMENTS(x) (sizeof(x)/sizeof(x[0]))
  30273.  
  30274. typedef struct {
  30275. const char *stack_name;
  30276. int *pstack;
  30277. size_t stack_ptr;
  30278. size_t max_stack;
  30279. } stack;
  30280.  
  30281. int array1[1];
  30282. static int array2[30];
  30283.  
  30284. stack stack1 = {"stack1", array1, 0, NUMELEMENTS(array1)};
  30285. stack stack2 = {"stack2", array2, 0, NUMELEMENTS(array2)};
  30286. stack stack3 = {"stack3", NULL, 0, 0};
  30287.  
  30288. /* End of File */
  30289.  
  30290.  
  30291. Listing 2
  30292. void push(stack *, int);
  30293. int pop(stack *);
  30294.  
  30295. main()
  30296. {
  30297. int size = 50;
  30298.  
  30299. stack3.pstack = malloc(size * sizeof(int));
  30300.  
  30301. if (stack3.pstack == NULL) {
  30302. printf("Can't allocate space for stack3\n");
  30303. exit(1);
  30304. }
  30305.  
  30306. stack3.max_stack = size;
  30307.  
  30308. push(&stack1, 10);
  30309. push(&stack1, 12);
  30310. push(&stack2, 15);
  30311. push(&stack3, 20);
  30312. printf("stk1: %d\n", pop(&stack1));
  30313. printf("stk2: %d\n", pop(&stack2));
  30314. printf("stk3: %d\n", pop(&stack3));
  30315. printf("stk3: %d\n", pop(&stack3));
  30316.  
  30317. return 0;
  30318. }
  30319.  
  30320. /* End of File */
  30321.  
  30322.  
  30323. Listing 3
  30324. void push(stack *st, int value)
  30325. {
  30326. if (st->stack_ptr == st->max_stack)
  30327. printf("Stack %s is full\n", st->stack_name);
  30328. else
  30329. st->pstack[st->stack_ptr++] = value;
  30330. }
  30331.  
  30332. int pop(stack *st)
  30333. {
  30334. if (st->stack_ptr == 0) {
  30335. printf("Stack %s is empty\n", st->stack_name);
  30336. return 0;
  30337. }
  30338.  
  30339. return st->pstack[-st->stack_ptr];
  30340. }
  30341.  
  30342. /* End of File */
  30343.  
  30344.  
  30345. Listing 4
  30346. #include <stdio.h>
  30347. #include <stdlib.h>
  30348.  
  30349. #define NUMELEMENTS(x) (sizeof(x)/sizeof(x[0]))
  30350. #define INT 1
  30351. #define LONG 2
  30352. #define DOUBLE 3
  30353.  
  30354. typedef struct {
  30355. const char *stack_name;
  30356. void *pstack;
  30357. const int type; /* type of entries */
  30358. size_t stack_ptr;
  30359. size_t max_stack;
  30360.  
  30361. } stack;
  30362.  
  30363. int array1[10];
  30364. static long array2[30];
  30365.  
  30366. stack stack1 = {"stack1", array1, INT, 0, NUMELEMENTS(array1)};
  30367. stack stack2 = {"stack2", array2, LONG, 0, NUMELEMENTS(array2)};
  30368. stack stack3 = {"stack3", NULL, DOUBLE, 0, 0};
  30369.  
  30370. /* End of File */
  30371.  
  30372.  
  30373. Listing 5
  30374. main()
  30375. {
  30376. int size = 50;
  30377. int vali = 10;
  30378. long vall = 456789;
  30379. double vald = 123.456;
  30380.  
  30381. stack3.pstack = malloc(size * sizeof(double));
  30382. if (stack3.pstack == NULL) {
  30383. printf("Can't allocate space for stack3\n");
  30384. exit(1);
  30385. }
  30386.  
  30387. stack3.max stack = size;
  30388.  
  30389. push(&stack1, &vali);
  30390. push(&stack2, &vall);
  30391. push(&stack3, &vald);
  30392. printf("stk1: %d\n", *((int *)pop(&stack1)));
  30393. printf("stk2: %ld\n", *((long *)pop(&stack2)));
  30394. printf("stk3: %f\n", *((double *)pop(&stack3)));
  30395.  
  30396. return 0;
  30397. }
  30398.  
  30399. /* End of File */
  30400.  
  30401.  
  30402. Listing 6
  30403. void push(stack *st, void *pvalue)
  30404. {
  30405. if (st->stack_ptr == st->max stack)
  30406. printf("Stack %s is full\n", st->stack_name);
  30407. else
  30408. switch (st->type) {
  30409.  
  30410. case INT:
  30411. ((int *)st->pstack)[st->stack_ptr++] = *(int *)pvalue;
  30412. break;
  30413.  
  30414. case LONG:
  30415. ((long *)st->pstack)[st->stack_ptr++] = *(long *)pvalue;
  30416. break;
  30417.  
  30418. case DOUBLE:
  30419. ((double *)st->pstack)[st->stack_ptr++] = *(double *)pvalue;
  30420.  
  30421. break;
  30422. }
  30423. }
  30424.  
  30425. void *pop(stack *st)
  30426. {
  30427. if (st->stack_ptr == 0) {
  30428. printf("Stack %s is empty\n", st->stack_name);
  30429. return 0;
  30430. }
  30431.  
  30432. switch (st->type) {
  30433.  
  30434. case INT:
  30435. return &((int *)st->pstack)[-st->stack_ptr];
  30436. break;
  30437.  
  30438. case LONG:
  30439. return &((long *)st->pstack)[-st->stack_ptr];
  30440. break;
  30441.  
  30442. case DOUBLE:
  30443. return &((long *)st->pstack)[-st->stack_ptr];
  30444. break;
  30445. }
  30446. }
  30447.  
  30448. /* End of File */
  30449.  
  30450.  
  30451. Listing 7
  30452. #include <stdio.h>
  30453.  
  30454. enum node_type {Error, Char, Int, Double, String};
  30455.  
  30456. typedef struct {
  30457. enum node_type type;
  30458. union {
  30459. char c;
  30460. int i;
  30461. double d;
  30462. char *s;
  30463. } value;
  30464. } node;
  30465.  
  30466. void push(node);
  30467. node pop(void);
  30468.  
  30469. /* End of File */
  30470.  
  30471.  
  30472. Listing 8
  30473. main()
  30474. {
  30475. node n;
  30476.  
  30477. n.value.d. = 9.87;
  30478. n.type. = Double;
  30479. push(n);
  30480.  
  30481.  
  30482. n.value.s = "some text";
  30483. n.type = String;
  30484. push(n);
  30485.  
  30486. n.value.i = 123;
  30487. n.type = Int;
  30488. push(n);
  30489.  
  30490. n.value.c = 'A';
  30491. n.type = Char;
  30492. push(n);
  30493.  
  30494. while (n = pop(), n.type != Error) {
  30495.  
  30496. switch (n.type) {
  30497.  
  30498. case Char:
  30499. printf("Char = %c\n", n.value.c);
  30500. break;
  30501.  
  30502. case Int:
  30503. printf("Int = %d\n", n.value.i);
  30504. break;
  30505.  
  30506. case Double:
  30507. printf("Double = %f\n", n.value.d);
  30508. break;
  30509.  
  30510. case String:
  30511. printf("String = %s\n", n.value.s);
  30512. break;
  30513. }
  30514. }
  30515.  
  30516. return 0;
  30517. }
  30518.  
  30519. /* End of File */
  30520.  
  30521.  
  30522. Listing 9
  30523. #include <stdio.h>
  30524.  
  30525. #define STACK_SIZE 10
  30526.  
  30527. static node stack[STACK_SIZE];
  30528. static size_t stack_ptr = 0;
  30529.  
  30530. void push(node n)
  30531. {
  30532. if (stack_ptr == STACK_SIZE)
  30533. printf("Stack is full\n");
  30534. else
  30535. stack[stack_ptr++] = n;
  30536. }
  30537.  
  30538. node pop(void)
  30539. {
  30540.  
  30541. static node error_node = {Error, 0};
  30542.  
  30543. if (stack_ptr == 0)
  30544. return error_node;
  30545. else
  30546. return stack[-stack_ptr];
  30547. }
  30548.  
  30549. /* End of File */
  30550.  
  30551.  
  30552. Listing 10
  30553. #include <stdio.h>
  30554.  
  30555. void push1(int);
  30556. void push2(int);
  30557. int pop1(void);
  30558. int pop2(void);
  30559. void dump_stack(void);
  30560.  
  30561. main()
  30562. {
  30563. dump_stack(); push1(10);
  30564. dump_stack(); push2(12);
  30565. dump_stack(); push1(15);
  30566. dump_stack(); push2(34);
  30567. dump_stack(); push1(99);
  30568. dump_stack(); push2(65);
  30569. dump_stack(); printf("stack 1: %d\n", pop1());
  30570. dump_stack(); printf("stack 1: %d\n", pop1());
  30571. dump_stack(); printf("stack 1: %d\n", pop1());
  30572. dump_stack(); printf("stack 2: %d\n", pop2());
  30573. dump_stack(); printf("stack 2: %d\n", pop2());
  30574. dump_stack(); printf("stack 2: %d\n", pop2());
  30575. dump_stack();
  30576.  
  30577. return 0;
  30578. }
  30579.  
  30580. /* End of File */
  30581.  
  30582.  
  30583. Listing 11
  30584. #include <stdio.h>
  30585.  
  30586. #define STACK_SIZE 4
  30587.  
  30588. static int stack[STACK_SIZE];
  30589.  
  30590. static size_t stack_ptr1 = 0;
  30591. static size_t stack_ptr2 = STACK_SIZE - 1;
  30592.  
  30593. /* End of file */
  30594.  
  30595.  
  30596. Listing 12
  30597. void push1(int value)
  30598. {
  30599. if (stack_ptr1 > stack_ptr2)
  30600.  
  30601. printf("Stack 1 is full\n");
  30602. else
  30603. stack[stack_ptr1++] = value;
  30604. }
  30605.  
  30606. void push2(int value)
  30607. {
  30608. if (stack_ptr1 > stack_ptr2)
  30609. printf("Stack 2 is full\n");
  30610. else
  30611. stack[stack_ptr2-] = value;
  30612. }
  30613.  
  30614. int pop1(void)
  30615. {
  30616. if (stack_ptr1 == 0) {
  30617. printf("Stack 1 is empty\n");
  30618. return 0;
  30619. }
  30620.  
  30621. return stack[-stack_ptr1];
  30622. }
  30623.  
  30624. int pop2(void)
  30625. {
  30626. if (stack_ptr2 == STACK_SIZE - 1) {
  30627. printf("Stack 2 is empty\n");
  30628. return 0;
  30629. }
  30630.  
  30631. return stack[++stack_ptr2];
  30632. }
  30633.  
  30634. void dump_stack(void)
  30635. {
  30636. int i;
  30637.  
  30638. printf("Stack contains: ");
  30639. for (i = 0; i < STACK_SIZE; ++i)
  30640. printf("%4d", stack[i]);
  30641.  
  30642. printf("\tsp1 = %lu, sp2 = %lu\n",
  30643. (unsigned long)stack_ptr1, (unsigned
  30644. long)stack_ptr2);
  30645. }
  30646.  
  30647. /* End of File */
  30648.  
  30649.  
  30650.  
  30651.  
  30652.  
  30653.  
  30654.  
  30655.  
  30656.  
  30657.  
  30658.  
  30659.  
  30660.  
  30661.  
  30662.  
  30663.  
  30664. On The Networks
  30665.  
  30666.  
  30667. It's Back!
  30668.  
  30669.  
  30670.  
  30671.  
  30672. Sydney S. Weinstein
  30673.  
  30674.  
  30675. Sydney S. Weinstein, CDP, CCP is a consultant, columnist, author, and
  30676. president of Datacomp Systems, Inc., a consulting and contract programming
  30677. firm specializing in databases, data presentation and windowing, transaction
  30678. processing, networking, testing and test suites, and device management for
  30679. UNIX and MS-DOS. He can be contacted care of Datacomp Systems, Inc., 3837
  30680. Byron Road, Huntingdon Valley, PA 19006-2320 or via electronic mail on the
  30681. Internet/Usenet mailbox syd@DSI. COM (dsinc!syd for those who cannot do
  30682. Internet addressing).
  30683.  
  30684.  
  30685. It's official. Paul Vixie has indeed taken over the moderation of
  30686. comp.sources.unix. He also has the assistance of two co-moderators, Mike Stump
  30687. and Nick Lai, to help him prevent the kind of backlog that occurred recently.
  30688. Submissions to the news group, and comments to the moderators should be sent
  30689. to unix-sources-moderator@pa.dec.com or decwrl!unix-sources-moderator. The
  30690. queue has been cleared, and the backlog posted. However, it includes over six
  30691. megabytes of sources in 37 separate postings. Here are just the highlights.
  30692. The first posting of the "new era" was chop from George Sicherman <gls@hrmso.
  30693. att.com>. chop extracts selected fields or columns of lines from the specified
  30694. files or standard input, and writes them to standard output. It is similar to
  30695. cut, a tool from the UNIX Documenters Workbench. chop allows for variable
  30696. field separators and for output of the fields in the order specified on the
  30697. command line. chop is Volume 25, Issue 1.
  30698. Victor Abell <obe@mace.cc.purdue.edu> has obsoleted two of his older postings,
  30699. ofiles and fstat, with a new version of lsof. LiSt Open Files displays the
  30700. names of files opened by processes on selected UNIX systems. This new version
  30701. in Volume 25, Issue 2, supports any file system based on the SunOS vnode
  30702. model. This includes SunOS, AIX, HP-UX, NeXTStep, Dynix and some others. It
  30703. understands most vnode extensions, NFS connections, FIFOs, multiplexed files,
  30704. and UNIX and INET sockets.
  30705. Abell having obsoleted ofiles, Robert Ehrlich <ehrlich@margaux. inria.fr> then
  30706. went and reworked the original ofiles and released ofiles2 for Volume 25,
  30707. Issue 72. This new version is more portable, but is still designed for BSD
  30708. derived UNIX systems. As with lsof, ofiles2 displays the names of files that
  30709. are being used by processes.
  30710. A major update to the "Threaded RN" Newsreader was issued by Wayne Davison
  30711. <davison@boreland. com> for Volume 25, Issues 4-16. TRN is based on RN 4.4,
  30712. and the posting includes both RN and the changes to make TRN from RN. TRN uses
  30713. the References header to build a discussion thread out of the news group. The
  30714. articles are then presented in discussion thread order.
  30715. One of the long promised postings, held in the queue for ages, is ease v3.5.
  30716. ease is a decompiler that converts sendmail.cf into a language that is much
  30717. easier to understand, and then allows editing of that intermediate language.
  30718. It also includes a compiler to convert the ease language back into a
  30719. sendmail.cf file. Considering how unreadable sendmail.cf files are, anything
  30720. is an improvement. However, ease is a vast improvement. B r u c e B a r n e t
  30721. t <barnett@crdgw1.ge.com> contributed ease for Volume 25 Issues 17-22.
  30722. W e n - K i n g S u <wenking@vlsi.cs.caltech.edu> contributed fsp, one of the
  30723. more interesting postings. It consists of a set of programs that implement a
  30724. public-access archive server similar to anonymous-ftp. While the actual code
  30725. is probably not of much use to most CUJ subscribers, the concept and the
  30726. implementation of that concept are a very well thought out lesson in
  30727. client/server computing. It shows the tradeoffs between multiple servers, and
  30728. a single stateless server. FSP was posted as Volume 25, Issues 24-26.
  30729. Several of the newer UNIX shells provide interactive command line editing. The
  30730. ile program from Robert C. Pendleton <bobp@hal.corn> provides this same
  30731. capability for any program. It runs the program as a subprocess so it does not
  30732. need to modify the program, nor does it even need the source of the program it
  30733. is servicing. It cannot provide a command history from program to program
  30734. however. ile is Volume 25, Issue 29.
  30735. One of the nicer capabilities of the Bitnet sites is the listserv program
  30736. provided to automate maintenance of mailing lists. Anastasios Kotsikonas
  30737. <tasos@cs.bu.edu> has provided similar capabilities in his listserv v5.31
  30738. package posted in Volume 25, Issues 35-40. listserv provides support for list,
  30739. list-owner, and list-request aliases for the mailing list and provides the
  30740. back end programs for archives, moderated lists, peer lists, automated
  30741. subscription, changes of address, and canceling of subscriptions.
  30742. I get spoiled by running on a bit-mapped screen with a good windowing system.
  30743. Just writing this column I have several windows open, one for the column, one
  30744. for the list of files to write about, and one containing the file I am
  30745. reviewing. For those stuck on standard ASCII terminals Juergen Weigert
  30746. <jnweiger@immd4. informatik. uni-erlangen.de> has released version 3 of his
  30747. multi-session package, screen3, for Volume 25, Issues 41-48. It allows several
  30748. different virtual screens on a single terminal using a short "hot key"
  30749. sequence to switch between the sessions.
  30750. One of the more popular UNIX shells for interactive use has been csh, the
  30751. c-shell. Over the years, many interactive extensions to csh were published as
  30752. tcsh. These extensions used to require the source of csh as the base for the
  30753. patches. Now with version 6, the full source of tcsh can be released. tcsh is
  30754. a "kitchen sink" shell. It supports command line editing, command and file
  30755. name completion, lists, manual lookup, job control, and has been ported to
  30756. many different UNIX systems. For those that prefer csh to ksh, then tcsh will
  30757. give you all the benefits of ksh with the csh syntax. The newest version,
  30758. 6.01, was contributed by Christos Zoulas <christos@ee.cornell. edu> for Volume
  30759. 25, Issues 54-71.
  30760. Another major update is the latest version of the Revision Control System. RCS
  30761. v5.6, which is a very flexible source code librarian not only supports source
  30762. files, but also can track changes to binary files. RCS v5.6 was contributed by
  30763. Adam Hammer <hammer@cs.purdue.edu> for Volume 25, Issues 77-87. New in v5.6
  30764. are changes to fix security problems, efficiency changes for retrieving older
  30765. versions, and the following of symbolic links instead of breaking them, and
  30766. more reliable lock files under NFS.
  30767. Emmet Gray <fthood!egray@uxc.cso.uiuc.edu> has updated his mtools package to
  30768. version 2.0.5. mtools allows UNIX systems read, write, and manipulate files on
  30769. an MS-DOS filesystem (typically a diskette). It emulates the commands ATTRIB,
  30770. CD, COPY, DEL/ERASE, DIR, FORMAT, LABEL, MD/MKDIR, RD/RMDIR, COPY, REN/RENAME,
  30771. TYPE, and COPY. The FORMAT command only adds the MS-DOS File system to the
  30772. diskette. It depends on the UNIX low-level format routines to actually low
  30773. level format the diskette. mtools2 was posted in Volume 25, Issues 97-99.
  30774. Volume 25, Issue 103 is a set of patches to mtools 2.0.5 from Henry van Cleef
  30775. <vancleef@netcom.netcom.com> to support XENIX 286 systems. There were
  30776. portability problems in the original release in regards to assuming that ints
  30777. are at least 32 bits long. In XENIX 286, an int is 16 bits.
  30778. Recent patches appearing in comp.sources.unix include:
  30779. psroff had patches 5, 6, and 7 posted as Volume 25, Issues 32, 33, and 104.
  30780. psroff allows both older C/A/T troffs and the newer di-troffs to work with
  30781. Postscript printers and with HP Laserjet printers. Patch 5 is minor fixes
  30782. mostly for compilation on 80286 style machines. Patch 6 is very important as
  30783. several major features were broken and fixed by this patch. Patch 7 is fixes
  30784. for groff/di-troff users using HP laserjets.
  30785. pathalias, the USENET map routing program, had patch 10 released by Peter
  30786. Honeyman <honey@citi.umich.edu> as Volume 25, Issue 89. This is purely a bug
  30787. fix patch and it is very small, but since so much depends on pathalias, I
  30788. though it worth mentioning.
  30789.  
  30790.  
  30791. No Reviews
  30792.  
  30793.  
  30794. The status postings show several projects in the re-review stage, but nothing
  30795. appeared in comp.sources.reviewed over the past two months. Now that
  30796. comp.sources.unix is back, this is not unexpected.
  30797.  
  30798.  
  30799. misc Tries an Experiment
  30800.  
  30801.  
  30802. Kent Landfield, the moderator of comp.sources.misc tried an experiment this
  30803. time. He took a very large posting, made a tar file of it, and then compressed
  30804. the tar file. He then uuencoded the compressed tar file and posted that. Even
  30805. posted that way it was 42 parts (plus part 00 with the text on what the
  30806. posting is and how to convert it back to a tar file). If posted as the normal
  30807. shar format it would have been over 140 parts, probably the largest single
  30808. posting ever tried. Reviews of this method were mixed, with a large amount of
  30809. complaints on how it is harder to determine what is there, and to sort and
  30810. deal with it, but I had no problems saving the files, running my concatenation
  30811. script and feeding that to uudecode to convert the ASCII back to binary. It
  30812. then uncompressed cleanly and produced a 7.5MB tar file.
  30813. And what was this grand experiment..., pp v6.0, a Mail Transfer Agent (MTA).
  30814. pp is designed for high volume message switching, protocol conversion, and
  30815. format conversion. pp supports X.400, RFC-822, and RFC-1148bis conversion
  30816. between RFC-822 and X.400. PP is designed as a replacement for MMDF or
  30817. sendmail. pp speaks X.400 (1984 and 1988), SMTP, JNT, UUCP, DECNET Mail-11,
  30818. X.500, alias files, RFC-822 local delivery, and Fax Internetworking. No User
  30819. Agents are provided, but a line oriented and an X-Window based management
  30820. console program are provided. pp v6.0 was contributed by Steve
  30821. Hardcastle-Kille <S.kille@cs.ucl.ac.uk> for Volume 27, Issues 24-66.
  30822. The shadow log-in suite for UNIX systems was rereleased by John F. Haugh II
  30823. <jfg@rpp386.cactus.org> for Volume 26, Issues 54-64. New in release 3 is
  30824. support for SVR4 style maintenance utilities and the grouping of the code into
  30825. libraries to make maintenance easier. This suite provides shadow
  30826. login/password management to many UNIX systems that do not yet have such
  30827. support natively. One file was left out of the distribution and was posted as
  30828. patch 1 in Volume 26, Issue 75.
  30829. Robert Davis<robert@am.dsir.govt.nz> contributed a new version of his C++
  30830. matrix package for Volume 26, Issues 87-91. newmat04 supports matrix, upper
  30831. and lower triangle, diagonal and symmetric matrices, row and column vectors
  30832. and the element type float/double. Operators include *, +, --, inverse,
  30833. transpose, submatrix, determinant, decomposition, triangularization,
  30834. eigenvalues, sorting and fast Fourier transforms. It is intended for matrices
  30835. in the size range 4x4 to 90x90.
  30836. Have an HP Laserjet with the Pacific Data Systems 25-in-One font cartridge? If
  30837. so, Bill Walker's <bkw@uecok.ecok.edu> wroff is what you need. It is a text
  30838. formatter in the spirit of nroff, but designed specifically for this
  30839. combination of hardware. wroff runs on UNIX, XENIX, MS-DOS and CPM-68K. It
  30840. does kerning and some other troff like items as well. wroff is Volume 26,
  30841. Issues 97-101.
  30842. Ted Campbell <tcamp@hercules.acpub.duke.edu> contributed sfs the space flight
  30843. simulator for IBM PC's with EGA or VGA, UNIX- PC's with MGR or UNIX with X11.
  30844. A 21-part posting in Volume 27, Issues 1-21, sfs offers a graphics-based
  30845. real-time animated simulation of orbital flight. You can simulate a complete
  30846. range of orbital parameters and can also simulate multiple planets in a solar
  30847. system. A particularly full map is given of the earth and can be displayed as
  30848. viewed from the orbiting spacecraft, as a ground track map, or as a distance
  30849. perspective in which the orbital track can be seen.
  30850. A compatible regex (regular expression) package without any licensing
  30851. restrictions was contributed by Tatu Ylonen <ylo@ngs.fi> for Volume 27, Issue
  30852. 23. It is fully compatible with the GNU regex library and can handle arbitrary
  30853. data including binary patterns. It also can compile and run on 16-bit machines
  30854. such as MS-DOS.
  30855. Those interested in genealogical research may want to get Steve Murphy's
  30856. <murf@oakhill.sps.mat.com> gcom from Volume 21, Issues 72-78. gcom reads in
  30857. GEDCOM format files containing genealogical data and merges them, utilizing
  30858. not only name and date match heuristics, but familial ties as well.
  30859. In my February column, I mentioned Archie, the service that keeps track of
  30860. which sites archive which data. Brendan Kehoe <brendan@cs.widener.edu> has
  30861. updated his archie client in Volume 27, Issues 79-84. Version 1.3 of archie is
  30862. his Prospero client for the archie service. Note, using this client requires
  30863. TCP/IP access to an Archie server, which means you must be on an Internet.
  30864. Last on the new release front is the latest release of dmake. Version 3.8
  30865. replaces version 3.7 and hopefully addresses all the little obscure bugs and
  30866. features that remained. Dennis Vadura <dvadura@plg.waterloo.edu> has provided
  30867. this version of the make utility. This package is the extended make ala BSD
  30868. 4.4 release including many more features than the traditional versions. It
  30869. includes support for UNIX, XENIX, MS-DOS, OS/2, and Atari-ST TOS. dmake 3.8 is
  30870. Volume 27, Issues 101-142.
  30871. On the patch front, patch 9 was issued for parseargs in Volume 26, Issues 65
  30872. and 66 by Brad Appleton <brad@ssd.csd.harris.com>. parseargs provides a set of
  30873. functions to parse command-line arguments. It can do much more than the getopt
  30874. variety of parser. Patch 9 is mostly bug fixes and was followed by patch 10 in
  30875. Volume 26, Issue 116 for a bit more cleanup.
  30876. qbatch from Alan Saunders <tharr!alan> had its patch 2 posted for Volume 26,
  30877. Issue 70 and patch 3 posted in Issue 85. Again, these were bug fix releases.
  30878. The KSH lookalike, pdksh, posted by Simon Gerraty <sjg@zen.void.oz.au> was
  30879. updated to patch 1 in Volume 26, Issues 71 and 72. The build process was
  30880. cleaned up and some portability issues were addresses.
  30881. The tin threaded newsreader had patches 6 and 7 posted by Iain Lea
  30882. <stevax!iain> in Volume 26, Issues 76-82. New are support for Minix 386, more
  30883. -M options for From lines, unread articles, and scrolling, plus some bug
  30884. fixes. Patch 6 was in five parts and patch 7 in two parts.
  30885. PBMPLUS the multi-format image manipulation toolset was also updated to fix
  30886. some bugs and to add several new programs. Jef Poskanzer <jef@gwell.sf.ca.us>
  30887. provided patch 10 in Volume 26, Issues 106-110. New are pgmcrater, ppmforge,
  30888. ppmtoacad, and sldtoppm.
  30889.  
  30890.  
  30891.  
  30892. Alternative Games
  30893.  
  30894.  
  30895. Two different versions of a "get the money game" called sokoban were
  30896. contributed by two different authors. Kevin Solie <kevins@ms.uky.edu>
  30897. contributed his xsokoban for Volume 13, Issues 1-2. This X-based game is a
  30898. very incomplete implementation of an idea from a similar PC game.
  30899. The other version, xsokoban2, is from Joseph Traub <jt1o+@andrew.cmu.edu> and
  30900. was released in Volume 13, Issues 13-15. It provides a slightly different user
  30901. interface than Kevin's version, but is essentially the same game.
  30902. Volume 13, Issue 3 provides dr_mario from Scott Noecker
  30903. <noecker@seq.uncwil.edu>. This is a one-player lookalike version of Dr. Mario,
  30904. a popular game for Nintendo that has nothing to do with the Mario Brothers
  30905. series. It uses the standard keyboard and curses for the display driver.
  30906. A complete revision of the WCST Nethack spoilers file updating it to version 7
  30907. was contributed for Volume 13, Issues 4-9 by Paul Waterman <wheaton!water>. A
  30908. complete reformatting has been done to make the information easier to use, and
  30909. of course, more changes and sections have been added.
  30910. A connect-five-in-a-row-game, make5 was contributed by Chih-Hung Hsieh
  30911. <hsiehch@spunky.cs.nyu.edu> for Volume 13, Issues 10-12. This game, written in
  30912. C++ provides both a curses and an X-Window interface. The X version uses the
  30913. athena widgets library.
  30914. For those with multi-user networks, a multi-player networked bridge game was
  30915. contributed by Matthew Clegg <mclegg@cs.ucsd.edu>. okbridge, Volume 13, Issues
  30916. 16-23, is a computer mediated bridge game that allows four players to
  30917. participate in a game of rubber or duplicate bridge. The program handles
  30918. dealing, scoring and communication of bids and plays. Issue 23 is a patch to
  30919. fix a small problem in one of the distribution files.
  30920.  
  30921.  
  30922. Previews from alt.sources
  30923.  
  30924.  
  30925. Its been a quiet two months in alt.sources, only 4MB worth of notable postings
  30926. (that or I am being more selective in what I consider notable). The most
  30927. notable posting is a release of a uucp work-alike package from Ian Lance
  30928. Taylor <ian@airs.cam>. taylor-uucp v1.01 was posted on November 24, 1991 in 18
  30929. parts. It is a complete replacement for HDB style UUCP. It supports V2 style
  30930. configuration (L.sys, L-devices) and BNU (aka HDB) style configuration files
  30931. (Systems, Devices). It is a complete system, except for the fancy maintenance
  30932. shell scripts and uusched (the latter is in the works).
  30933. Curt Mayer <hoptoad!curt> posted his disassembler for Z80/Z280 CP/M .COM files
  30934. on November 27, 1991. The output is capable of being assembled and can detect
  30935. code sequences by tracing jump targets.
  30936. Along with the CP/M disassembler, D'Arcy J. M. Cain <druid!darcy> posted on
  30937. December 19, 1991 in three parts his Z80 CP/M emulator for UNIX. Most of the
  30938. Z80 instruction set is implemented, except for the I/O section. The emulator
  30939. also uses the UNIX shell commands to simulate some of the CP/M commands and
  30940. uses the UNIX file system for drives. All I/O must go via the BDOS or BIOS as
  30941. IN and OUT opcodes are not fully implemented.
  30942. On the opposite front, Quinn Jenson <jensenq@qcj.icon.com> posted a DSP56001
  30943. assembler on November 29, 1991 in four parts. The syntax was intended to be
  30944. compatible with Motorola's syntax, but without the docs he could only guess.
  30945. It does allow for UNIX based DSP code development for those not lucky enough
  30946. to have a NeXT.
  30947. pcal, the postscript calendar program has been updated to Version 4.3 by
  30948. Joseph Brownlee <jbrO@cbnews.cb.art.com> on December 16, 1991 in seven parts.
  30949. pcal allows for creation of personal calendars. New in 4.3 are generation of
  30950. UNIX calendar files, move the previous and following month boxes around on the
  30951. page, allow notes in any empty day box, change both note font and size via
  30952. command line options, addition of the nearest keyword as in "workday nearest
  30953. every 10th", plus about a page more worth of additions (something had to fill
  30954. the 13000 lines of the posting).
  30955. Mayan Moudgill <moudgill@cs.cornell.edu> has posted his C++ socket library
  30956. that adds UNIX and INET sockets to the iostreams functions. It was posted in
  30957. late December, and then an updated posting was made on January 8, 1992 in two
  30958. parts. Supported are convenient connection setup routines, use of the usual <<
  30959. & >> operators for i/o, support for out of band message transmission and
  30960. reception, and an easy method of specifying per socket SIGIO, SIGURG and
  30961. SIGPIPE handlers. Also include is an interface to the select system.
  30962. A gateway between the UNIX and the Citadel style BBS was posted on December
  30963. 24, 1991 in four parts by Ken MacLeod <unidel@bitsko.slc.ut.us>. uccico
  30964. implements the Citadel BBS networking for a UNIX system much like uucico does
  30965. for UUCP. It handles USENET news and RFC822 mail conversion.
  30966. A large posting was mixview, posted by Robert Lau but written by Douglas Scott
  30967. <doug@woof.columbia.edu> in 11 parts on January 12, 1992. It is an X-Window
  30968. program that allows for editing sound files. It supports any BSD style system
  30969. that has sound capabilities, such as Sun's and NeXT's.
  30970.  
  30971.  
  30972.  
  30973.  
  30974.  
  30975.  
  30976.  
  30977.  
  30978.  
  30979.  
  30980.  
  30981.  
  30982.  
  30983.  
  30984.  
  30985.  
  30986.  
  30987.  
  30988.  
  30989.  
  30990.  
  30991.  
  30992.  
  30993.  
  30994.  
  30995.  
  30996.  
  30997.  
  30998.  
  30999.  
  31000.  
  31001.  
  31002.  
  31003.  
  31004.  
  31005.  
  31006.  
  31007.  
  31008.  
  31009.  
  31010. Questions & Answers
  31011.  
  31012.  
  31013. Calling Functions from Within a Function
  31014.  
  31015.  
  31016.  
  31017.  
  31018. Ken Pugh
  31019.  
  31020.  
  31021. Kenneth Pugh, a principal in Pugh-Killeen Associates, teaches C language
  31022. courses for corporations. He is the author of C Language for Programmers and
  31023. All On C, and was a member on the ANSI C committee. He also does custom C
  31024. programming for communications, graphics, image databases, and hypertext. His
  31025. address is 4201 University Dr., Suite 102, Durham, NC 27707. You may fax
  31026. questions for Ken to (919) 489-5239. Ken also receives email at kpugh@dukemvs.
  31027. ac. duke.edu (Internet).
  31028.  
  31029.  
  31030. Q
  31031. I work with ANSI-C and I have a general problem. How is it possible to call
  31032. one function with a variable number/types of arguments inside a second
  31033. function with variable number/types of arguments, with the same arguments I
  31034. called the second function?
  31035. For example, I have three functions with a variable number and variable type
  31036. of arguments (myfunction1, myfunction2, myfunction3 shown in Listing 1). I
  31037. want to call the functions myfunction1 and myfunction2 from inside
  31038. myfunction3, with the same arguments I called myfunction3. Is there an easy
  31039. way to do that?
  31040. Thank you very much for your answer.
  31041. Willi Fleischer
  31042. Moerfelden, Germany
  31043. A
  31044. The error involves the difference between a variable parameter list and a
  31045. parameter list containing a value of type va_list. When you pass parameters to
  31046. a function, the values of those parameters are pushed onto the stack. Usually
  31047. the first or second parameter to a variable parameter function indicates
  31048. directly (with a count) or indirectly (as with format specifiers) how many
  31049. values have been passed.
  31050. The printf and fprintf functions expect to see the values on the stack. Each
  31051. value has an address (its position on the stack). The vfprintf function
  31052. expects its third parameter will be the address of the first value of a set of
  31053. parameters on the stack. It then uses this address to retrieve those
  31054. parameters. To use each parameter, it needs to know the type of the value.
  31055. That information it gets from the format list specifiers, just like printf and
  31056. fprintf. The type of the parameter is also used to find the next parameter.
  31057. Perhaps it might be instructive to look at typical definitions for these
  31058. macros. Those in Listing 2 are from Microsoft C.
  31059. Variables of type va_list are actually addresses. va_start puts into the first
  31060. argument the address of the first variable parameter. va_arg increments the
  31061. first argument (ap) by the sizeof the type which is passed as the second
  31062. argument (t), as well as yielding a value of that type.
  31063. How does vfprintf get to a value on the stack? Since it knows the type of
  31064. argument (from the format list), it uses the va_arg macro with the appropriate
  31065. type.
  31066. Your my_function1 and my_function2 require a list of values to be passed to
  31067. them. When you used the va_start macro in my_function3, you retrieved the
  31068. address of the first variable parameter that was passed to my_function3. You
  31069. then passed that address to my_function1 and my_function2 and they produced
  31070. garbage. You need to rewrite my_function1 and my_function2 such that they
  31071. expect an address (i.e. type va_list). Listing 3 shows how they should look.
  31072. Q
  31073. I've been programming in C for about two years now and have not seen anything
  31074. in the literature about my question. The company I work for does quick and
  31075. dirty production programming. We are in the process of moving from PL/I to C.
  31076. The programming involves sequential processing of mostly fixed fielded files.
  31077. In PL/I we read the file into an input buffer and use the string function to
  31078. load it into a structure. Listing 4 is an instance.
  31079. What this in effect does is load the structure recin from the input buffer
  31080. inn. There is no corresponding C function for loading such a structure and
  31081. I've been trying to develop something that will do this. What I've come up
  31082. with is Listing 5.
  31083. While the coding for load_struct works (at least in VAX C) I am not convinced
  31084. this is the best way or even an effective way for doing what I desire. I
  31085. realize the error checking is non-existent, but is it correct to assume that a
  31086. structure of character arrays will have contiguous addresses? Is there another
  31087. method used by more experienced programmers? I need something that I can put
  31088. into a library that is very generic. How would you tackle this problem?
  31089. Tom Crosman
  31090. Brooklyn Park, MN
  31091. A
  31092. On some machines individual fields in a structure do have packing bytes
  31093. between them to align them to word boundaries, giving them contiguous
  31094. addresses. Records which consist of character only (such as your example) tend
  31095. not to have packing bytes.
  31096. Your method in general is fine. Since you asked for my solution, I've given it
  31097. in Listing 6. Let me explain the modifications that I've made. The first is
  31098. that one should usually never declare a variable in the same statement that
  31099. declares the structure template. Anyone who wishes to use that template gets
  31100. stuck with that extra variable. Second, I used an array of sizes for each of
  31101. the fields. These could actually be picked up using the sizeof operator. As
  31102. another alternative, one could #define the size of each field and use those in
  31103. the initialization list.
  31104. I prefer using an array for the sizes instead of passing them in the parameter
  31105. list. The declaration can be close to the structure template. Any changes in
  31106. order or size of the fields can be simply coordinated. Using an array to pass
  31107. the sizes also simplifies the function, since it is no longer concerned with a
  31108. variable parameter list.
  31109. To show an additional use for the array, I included a print function. It
  31110. requires most of the same parameters as the load function.
  31111. To make the function more generic, I added a nul_terminated flag. Some
  31112. programmers do not like using the extra character space to hold a terminator
  31113. in each field. The nul, if present, can signify the end of a value less than
  31114. the field length. If not present, the field length is the size of the field.
  31115. Though this requires a slight bit more coding, it does save significant disk
  31116. space if you store thousands of copies of a structure.
  31117. If you were concerned with the packing of the fields in either the input
  31118. record or the output structure, then you could add an array of field addresses
  31119. to the calls. If that is necessary, I might suggest not using a generic
  31120. function and simply hard coding any record conversions required. At some point
  31121. the work of providing and using a generic interface exceeds the benefit.
  31122.  
  31123.  
  31124. Reader Feedback
  31125.  
  31126.  
  31127.  
  31128.  
  31129. Coding style
  31130.  
  31131.  
  31132. I notice in your find_maximum function that you declare temporary storage for
  31133. the return value. Is there some reason this is preferred over just returning
  31134. the value directly? This allows the simplifications in Listing 7.
  31135. Also in your listings, for the function put_line, it seems to me that putchar
  31136. would be preferred to printf("%c",...). It would not have to process the
  31137. format string and convert the character before putting it on stdout.
  31138. I enjoy your column and comments.
  31139. Edward C. Sarlls, III
  31140. Houston, TX
  31141. I tend to use an automatic variable for the return value from a function. That
  31142. makes it easier to put a printf statement in the code to print the return
  31143. value of the function. Or if you are a debugger person, it makes it easy to
  31144. watch the value.
  31145. The disadvantage is a slight decrease in speed. If I made it a register
  31146. variable (or if the compiler does so automatically), even that should not be a
  31147. problem.
  31148.  
  31149. If you decide to change the return value of the function that uses an
  31150. expression, and then with the local parameter, you only have to change the
  31151. expression in one place. If you have debugging output, you need to change it
  31152. in two.
  31153. I must admit that I have had this style for a long long time. In one of my C
  31154. classes that I taught back in the early '80s, I had a student who insisted
  31155. that parentheses were required around the expression that follows the return
  31156. statement. It turns out that all the examples of return statements he had seen
  31157. had complex expressions around them (as the one in your second example).
  31158. Psychologically the parenthesis were needed to surround the expression and
  31159. "make it one." That points up the other coding style that I use quite often
  31160. (see Listing 8). I state in All on C that having a single return statement
  31161. with gotos is preferable to having multiple return statements. If I want to
  31162. trace the return value, with multiple return statements, I have to do
  31163. something like the code shown in Listing 9.
  31164. If I were using a debugger, there would be several breakpoints to set
  31165. (assuming the function was long enough that I simply didn't single step
  31166. through it).
  31167. The difference in the complexity of code between multiple returns and multiple
  31168. gotos (to the end of a function) does not seem to be a big issue, at least to
  31169. me.
  31170. I may get hundreds of letters regarding this seemingly idiosyncratic style of
  31171. programming (or maybe with an adjective using only the first two syllables).
  31172. Before that occurs, I wish to make a few caveats regarding it. First, I try to
  31173. program the logic not to require multiple returns/gotos. Hence the original
  31174. listing has neither in it. Second, there should be a single label at the end
  31175. of the function with a standard name (say end), that is the label for the
  31176. goto. If that is the case, then ret = xxx; goto end takes on the same meaning
  31177. as return xxx;.(KP)
  31178. Q
  31179. I just read your question and answer article entitled "Using typedef" in the
  31180. December 1991 issue of The C Users Journal. I am writing in reference to the
  31181. question regarding the writing of a function returning the lowest and highest
  31182. integer out of the three integers passed. Listing 10 is my attempt at
  31183. answering this question.
  31184. With all due respect, I think this approach is more eloquent and less
  31185. convoluted than the methods offered in Listing 2 (December 1991 issue) and
  31186. Listing 3 on page 120 (December 1991 issue) and in Listing 4 on page 122
  31187. (December 1991 issue), whether or not you choose to use the conditional
  31188. shorthand.
  31189. I am also puzzled by the fact that you were a member of the ANSI C committee
  31190. and you didn't use prototypes in your code. Did you have a special reason for
  31191. not prototyping your examples?
  31192. S.J. Stern
  31193. Bothell, WA
  31194. A
  31195. Eloquence is in the eye of the beholder. Some people might consider the second
  31196. part of Listing 7 (from Mr. Sarlls letter previously in this column) the most
  31197. eloquent. I don't particularly prefer any of my Listing 2, Listing 3, or
  31198. Listing 4. My favorite is Listing 5 (from the December 1991 issue, reproduced
  31199. here as Listing 11). It computes the maximum for any number of input
  31200. parameters. The logic in your sample matches the logic in that listing.
  31201. I guess I could have arranged the logic in my function as shown in Listing 12.
  31202. It matches your logic and has fewer lines than my previous listing. Unless I
  31203. am going to call a routine a few thousand times, I tend to stick with whatever
  31204. I come up with first that works. Also, I usually avoid using the conditional
  31205. expression operator, for the reasons explained in last month's column.
  31206. As far as prototypes, I usually do not include them unless they are required
  31207. by ANSI C or they are essential to the answer. The information contained in
  31208. prototypes is mostly redundant. The case of the variable parameter function
  31209. (as in Listing 10) requires one. When I do need prototypes (e.g. for C++), I
  31210. let the compiler or PC-Lint generate a file of them.
  31211. Thank you for your feedback.(KP)
  31212.  
  31213.  
  31214. typedefs and lint
  31215.  
  31216.  
  31217. In my column a few months ago, I answered a question regarding typedefs. At
  31218. the recent C-Forum sponsored by the Wang Institute of Boston University, I
  31219. bumped into Jim Gimpel, the author of PC-Lint. He told me that the latest
  31220. version of PC-Lint has an option for strong typing. This means that it can
  31221. report errors in the use of typedefed variables which the compiler would just
  31222. ignore. For example, given the code in Listing 13, the assignment of
  31223. short_distance = high_speed;
  31224. is accepted by the compiler without question since both variables are declared
  31225. to be type double, once the typedefs are resolved into the underlying types.
  31226. However PC-Lint can yield an error message, if strong typing is turned on.
  31227. As another example, a function declared as shown in Listing 14 will give a
  31228. PC-Lint error if it is passed the code in Listing 15.
  31229. There are many options available for strong typing, which are all described in
  31230. the manual. In addition, you can declare that particular arrays can only be
  31231. indexed by variables of particular types. For example, arrays of type
  31232. HISTOGRAM can only be indexed by variables of type INDEX. This would look
  31233. something like Listing 16.
  31234. The not_good_ index reference to my_array would yield an output error.
  31235. For those who are considering changing to C++ purely for its type-checking
  31236. abilities, I suggest you look at PC-Lint as an alternative.(KP)
  31237.  
  31238. Listing 1
  31239. #include <stdio.h>
  31240. #include <stdlib.h>
  31241. #include <stdarg.h>
  31242.  
  31243. void myfunction1(char *format, ...)
  31244. {
  31245. va_list arg_ptr;
  31246. va_start(arg_ptr,format);
  31247. vfprintf(stdout,format,arg_ptr);
  31248. va_end(arg_ptr);
  31249. }
  31250.  
  31251. void myfunction2(char *format, ...)
  31252. {
  31253. FILE *fp;
  31254. va_list arg_ptr;
  31255. fp = fopen("TEST.DAT","a+");
  31256. va_start(arg_ptr,format);
  31257. vfprintf(fp,format,arg_ptr);
  31258. va_end(arg_ptr);
  31259. fclose(fp);
  31260. }
  31261.  
  31262. void myfunction3(char *format, ...)
  31263. {
  31264. va_list arg_ptr1;
  31265. va_list arg_ptr2;
  31266. va_start(arg_ptr1,format);
  31267.  
  31268.  
  31269. myfunction1(format,arg_ptr1);
  31270.  
  31271. /* here I want to use the arguments of myfunction3(),
  31272. va_end(arg_ptr1); but this code does not work */
  31273.  
  31274. va_start(arg_ptr2,format);
  31275. myfunction2(format,arg_ptr2);
  31276.  
  31277. /* here I want to use the arguments of myfunction3(),
  31278. va_end(arg_ptr2); but this code does not work */
  31279. }
  31280.  
  31281. int main()
  31282. {
  31283. char msg[]="message";
  31284. myfunction1("\n%s=%d=%f", msg, 2, 3.0); /* this works
  31285. fine */
  31286. myfunction2("\n%s=%d=%f", msg, 2, 3.0); /* this works
  31287. fine */
  31288.  
  31289. /* the following call of myfunction3() does not work.
  31290. I want to have the same result, as
  31291. if I call myfunction1() and myfunction2() isolated */
  31292.  
  31293. myfunction3("\n%s=%d=%f", msg, 2, 3.0);
  31294. }
  31295.  
  31296. /* End of File */
  31297.  
  31298.  
  31299. Listing 2
  31300. typedef void *va_list;
  31301. #define va_start(ap,v) ap = (va_list)&v + sizeof(v)
  31302. #define va_arg(ap,t) ((t*)(ap += sizeof(t)))[-1]
  31303. #define va_end(ap) ap = NULL
  31304. /* End of File */
  31305.  
  31306.  
  31307. Listing 3
  31308. void myfunction1_a(char *format, va_list arg_ptr)
  31309. {
  31310. vfprintf(stdout,format,arg_ptr);
  31311. }
  31312.  
  31313. void myfunction2_a(char *format, va_list arg_ptr)
  31314. {
  31315. FILE *fp;
  31316.  
  31317. fp = fopen("TEST.DAT","a+");
  31318. vfprintf(fp,format,arg_ptr);
  31319. fclose(fp);
  31320. }
  31321.  
  31322. void myfunction3(char *format, ...)
  31323. {
  31324. va_list arg_ptr1;
  31325. va_list arg_ptr2;
  31326.  
  31327. va_start(arg_ptr1,format);
  31328.  
  31329. myfunction1_a(format, arg_ptr1);
  31330. myfunction2_a(format,arg_ptr1);
  31331. va_end(arg_ptr1);
  31332. }
  31333. /* End of File */
  31334.  
  31335.  
  31336. Listing 4
  31337. DCL 1 RECIN,
  31338. 2 NAME CHAR(30),
  31339. 2 ADDRESS CHAR(20),
  31340. 2 city CHAR(15),
  31341. 2 STATE CHAR(2),
  31342. 2 ZIP CHAR(5);
  31343.  
  31344. ...
  31345.  
  31346. READ FILE (FILIN) INTO (INN);
  31347. STRING(RECIN) = INN;
  31348. ...
  31349. /* End of File */
  31350.  
  31351.  
  31352. Listing 5
  31353. #define MAX 1000
  31354.  
  31355. void load_struct(instruc, instr, ...);
  31356.  
  31357. /************** main function ********/
  31358.  
  31359. load_struct(instruc, instr, va_alist)
  31360. char *instruc;
  31361. char *instr;
  31362. {
  31363. int k=0;
  31364. int strnglen;
  31365. va_list ap;
  31366. va_start(ap);
  31367.  
  31368. while((strnglen = va_arg(ap, int)) != NULL)
  31369. {
  31370. for(k = 0; k < strnglen-1; k++)
  31371. *instruc++ = *instr++;
  31372.  
  31373. *instruc++ = 0;
  31374. }
  31375. va_end(ap);
  31376. }
  31377. /* End of File */
  31378.  
  31379.  
  31380. Listing 6
  31381. struct rec {
  31382. char name[31];
  31383. char address[21];
  31384. char city[16];
  31385. char state[3];
  31386. char zip[6];
  31387. };
  31388.  
  31389. static int field_sizes[] = {31, 21, 16, 3, 6};
  31390. /*
  31391. Alternatively, this could be written as:
  31392.  
  31393. static int field_sizes[] = { sizeof(rec.name),
  31394. sizeof(rec.address),
  31395. ...
  31396. };
  31397. */
  31398. #define field_count (sizeof(field_sizes)/sizeof(int));
  31399.  
  31400. main()
  31401. {
  31402. char record_in[MAX];
  31403. struct rec record;
  31404. ...
  31405. while(fgets(record_inn, MAX, filin) != NULL)
  31406. {
  31407. load_struct(&record, record_in, field_sizes,
  31408. field_count, TRUE);
  31409. print_struct(&record, field_sizes, field_count, TRUE);
  31410. ...
  31411. }
  31412. }
  31413.  
  31414. print_struct(record, field_sizes, field_count, nul_terminated)
  31415. char *record; /* Record to print */
  31416. int field_sizes[]; /* Size of fields */
  31417. int field_count; /* Number of fields */
  31418. int nul_terminated; /* If fields are nul terminated */
  31419. {
  31420. int field;
  31421. int length;
  31422. char *pc;
  31423. printf("\n");
  31424. pc = record;
  31425. for (field = 0; field < field_count; field++)
  31426. {
  31427. if (nul_terminated)
  31428. printf("%s:",pc);
  31429. else
  31430. {
  31431. length = field_sizes[field];
  31432. printf("%.*s:", length, pc);
  31433. }
  31434. pc += field_sizes[field];
  31435. }
  31436. }
  31437.  
  31438. load_struct(record, record_in, field_sizes, field_count,
  31439. nul_terminated)
  31440. char *record;
  31441. char *record_in;
  31442. int field_sizes[];
  31443. int field_count;
  31444. int nul_terminated; /* If fields out should be nul terminated */
  31445. {
  31446. int field;
  31447. int length;
  31448.  
  31449. char *pc;
  31450. char *pc_in;
  31451.  
  31452. pc = record;
  31453. pc_in = record_in;
  31454. for (field = 0; field < field_count; field++)
  31455. {
  31456. length = field_sizes[field];
  31457. strncpy(pc, pc_in, length);
  31458. if (nul_terminated)
  31459. {
  31460. pc[length-1] = '\0';
  31461. pc_in += length - 1;
  31462. }
  31463. else
  31464. pc_in += length;
  31465. pc += field_sizes[field];
  31466. }
  31467. }
  31468.  
  31469. /* End of File */
  31470.  
  31471.  
  31472. Listing 7
  31473. int find_maximum(one, two, three)
  31474. int one, two, three;
  31475. {
  31476. if (one > two)
  31477. if (one > three)
  31478. return one;
  31479. else
  31480. return three;
  31481. if (two > three)
  31482. return two;
  31483. else
  31484. return three;
  31485. }
  31486.  
  31487. ... or ...
  31488.  
  31489. int find_maximum(one, two, three)
  31490. int one, two, three; /* This could be a macro if you're
  31491. careful about side effects. */
  31492. return ((one>two)?((one>three)?one:three):((two>three)?two:three));
  31493.  
  31494. /* End of File */
  31495.  
  31496.  
  31497. Listing 8
  31498. int find_maximum(one, two, three)
  31499. int one, two, three;
  31500. {
  31501. int ret;
  31502. if (one > two)
  31503. if (one > three)
  31504. {
  31505. ret = one;
  31506. goto end;
  31507. }
  31508.  
  31509. else
  31510. {
  31511. ret = three;
  31512. goto end;
  31513. }
  31514. ...
  31515. end:
  31516. printf("find_maximum returning %d", ret);
  31517. return ret;
  31518. }
  31519. /* End of File */
  31520.  
  31521.  
  31522. Listing 9
  31523. if (one > three)
  31524. {
  31525. printf("Find maximum returning %d", one);
  31526. return one;
  31527. }
  31528.  
  31529. /* End of File */
  31530.  
  31531.  
  31532. Listing 10
  31533. void get_low_high(int a, int b, int c, int *low, int *high)
  31534. {
  31535. *low = (a < b) ? a : b;
  31536. if (c < *low) *low = c;
  31537. *high = (a > b) ? a : b;
  31538. if (c > *high) *high = c;
  31539. }
  31540.  
  31541. /* End of File */
  31542.  
  31543.  
  31544. Listing 11
  31545. #include <stdarg.h>
  31546. int maximum(int count, int first,...);
  31547. main()
  31548. {
  31549. int ret;
  31550. ret = maximum(6, 2,3,4,5,9,8);
  31551. printf("Maximum is %d\n", ret);
  31552. }
  31553.  
  31554. int maximum(int count,int first,...)
  31555. {
  31556. va_list arguments;
  31557. int i;
  31558. int value;
  31559. int maximum = first;
  31560. va_start(arguments, first);
  31561. for (i = 0; i < count - 1; i++)
  31562. {
  31563. value = va_arg(arguments, int);
  31564. if (value > maximum)
  31565. maximum = value;
  31566. }
  31567. va_end(arguments);
  31568.  
  31569. return maximum;
  31570. }
  31571. /* End of File */
  31572.  
  31573.  
  31574. Listing 12
  31575. void find_min_max(one, two, three, minimum, maximum)
  31576. int one, two, three;
  31577. int *minimum;
  31578. int *maximum;
  31579. {
  31580. if (one > two)
  31581. *minimum = two;
  31582. else
  31583. *minimum = one;
  31584. if (three < *minimum)
  31585. *minimum = three;
  31586.  
  31587. if (one < two)
  31588. *maximum = two;
  31589. else
  31590. *maximum = one;
  31591. if (three > *maxmium)
  31592. *maximum = three;
  31593.  
  31594. return;
  31595. }
  31596. /* End of File */
  31597.  
  31598.  
  31599. Listing 13
  31600. typedef double SPEED;
  31601. typedef double DISTANCE;
  31602. typeder double TIME;
  31603.  
  31604. SPEED low_speed, high_speed;
  31605. DISTANCE short_distance, long_distance;
  31606. TIME brief_time, long_time;
  31607.  
  31608. /* End of File */
  31609.  
  31610.  
  31611. Listing 14
  31612. SPEED compute_speed(DISTANCE distance, TIME time);
  31613.  
  31614. /* End of File */
  31615.  
  31616.  
  31617. Listing 15
  31618. low_speed = compute_speed(long_time, short_distance);
  31619.  
  31620. /* End of File */
  31621.  
  31622.  
  31623. Listing 16
  31624. /* lint -index(c,INDEX,HISTOGRAM) */
  31625.  
  31626. typedef unsigned int INDEX;
  31627. typedef int HISTOGRAM
  31628.  
  31629. #define SIZE (INDEX) 10
  31630.  
  31631. HISTOGRAM my_array[SIZE];
  31632. INDEX good_index;
  31633. int not_good_index;
  31634.  
  31635. my_array[good_index] = (HISTOGRAM) 5;
  31636. my_array[not_good_index] = (HISTOGRAM) 7;
  31637.  
  31638. /* End of File */
  31639.  
  31640.  
  31641.  
  31642.  
  31643.  
  31644.  
  31645.  
  31646.  
  31647.  
  31648.  
  31649.  
  31650.  
  31651.  
  31652.  
  31653.  
  31654.  
  31655.  
  31656.  
  31657.  
  31658.  
  31659.  
  31660.  
  31661.  
  31662.  
  31663.  
  31664.  
  31665.  
  31666.  
  31667.  
  31668.  
  31669.  
  31670.  
  31671.  
  31672.  
  31673.  
  31674.  
  31675.  
  31676.  
  31677.  
  31678.  
  31679.  
  31680.  
  31681.  
  31682.  
  31683.  
  31684.  
  31685.  
  31686.  
  31687.  
  31688.  
  31689.  
  31690.  
  31691.  
  31692. CUG New Releases
  31693.  
  31694.  
  31695. Gadgets
  31696.  
  31697.  
  31698.  
  31699.  
  31700. Kenji Hino
  31701.  
  31702.  
  31703. Kenji Hino is a member of The User's Group technical staff. He holds a B.S.C.S
  31704. from McPherson College and an undergraduate degree in metallurgy from a
  31705. Japanese university. He enjoys playing drums in a reggae band.
  31706.  
  31707.  
  31708.  
  31709.  
  31710. Update
  31711.  
  31712.  
  31713.  
  31714.  
  31715. CUG297 Small Prolog
  31716.  
  31717.  
  31718. Henri de Feraudy (FRANCE) has released v2.0 of his Small Prolog. The new
  31719. version offers much better debugging facilities, and a 32-bit executable
  31720. compiled by GCC-386 (CUG359).
  31721.  
  31722.  
  31723. CUG327 Panels for C
  31724.  
  31725.  
  31726. J. Brown (KS) has released v2.3 update of his shareware window package, Panels
  31727. for C. This version includes these new features: OS/2 support, Turbo C
  31728. support, utilizing the PATH environment variables to find panel definition
  31729. files, allowing the inclusion of panel definitions in the C source program,
  31730. Interactive Panel Design (IPD) utility.
  31731.  
  31732.  
  31733. CUG335 UltraWin
  31734.  
  31735.  
  31736. Kevin L. Huck (MO) has released v2.10 of his shareware, Ultrawin. This new
  31737. version includes new features: unlimited overlapping windows, background
  31738. printing, PC timer control, mouse and graphic support, and enhanced data entry
  31739. capabilities. Also included are a hypertext help engine and and EGA/VGA font
  31740. editor. Also released is InTUItion 1.10, a textual user-interface library that
  31741. includes an interface contruction program that allows UltraWin users to
  31742. interactively create dialog boxes, menus, pick lists, forms and more using a
  31743. mouse. Source code can be automatically generated to perform processing on
  31744. each item, saving hours of tedious hand coding and debugging.
  31745.  
  31746.  
  31747. New Releases
  31748.  
  31749.  
  31750.  
  31751.  
  31752. CUG361 Gadgets and Term
  31753.  
  31754.  
  31755. Jack E. Ekwall has contributed a function library Gadgets, a group of
  31756. UNIX-like tools for DOS; and Term, a collection of computer buzz-words.
  31757. Gadgets provides functions such as popup/dropdown window, drawing box, screen
  31758. and cursor manipulation, keyboard input, color, date, printer and mouse
  31759. control, and file manipulation. Some of the functions are lifted from CUG273
  31760. Turbo C Utilities. The library is linkable to Turbo C v2.0. These UNIX-like
  31761. tools offer a solution to the DOS command line interface pipeline problem.
  31762. Term includes 634 topics and 32 historical notes/ observations about computer
  31763. buzzwords. This text is in a text-indexed sequential form which can be read by
  31764. a display program, VU. The distribution disk includes source code for the
  31765. library and documentation.
  31766.  
  31767.  
  31768.  
  31769.  
  31770.  
  31771.  
  31772.  
  31773.  
  31774.  
  31775.  
  31776.  
  31777.  
  31778.  
  31779.  
  31780.  
  31781.  
  31782.  
  31783.  
  31784.  
  31785.  
  31786.  
  31787.  
  31788.  
  31789.  
  31790.  
  31791.  
  31792.  
  31793.  
  31794.  
  31795.  
  31796.  
  31797.  
  31798.  
  31799.  
  31800.  
  31801.  
  31802.  
  31803.  
  31804.  
  31805.  
  31806.  
  31807.  
  31808.  
  31809.  
  31810.  
  31811.  
  31812.  
  31813.  
  31814.  
  31815.  
  31816.  
  31817.  
  31818.  
  31819.  
  31820.  
  31821.  
  31822.  
  31823.  
  31824.  
  31825.  
  31826.  
  31827.  
  31828.  
  31829.  
  31830.  
  31831.  
  31832.  
  31833.  
  31834.  
  31835.  
  31836.  
  31837.  
  31838.  
  31839. Zinc Interface Library
  31840.  
  31841.  
  31842. David Brumbaugh
  31843.  
  31844.  
  31845. David Brumbaugh is a project manager at Advanced Information Services,
  31846. asystems integrator in Peoria, IL. He has been programming in C for over
  31847. fiveyears and in C++ for over a year. He can be reached by mail at 2807 N.
  31848. Renwood Ave., Peoria, IL 61604.
  31849.  
  31850.  
  31851.  
  31852.  
  31853. Introduction
  31854.  
  31855.  
  31856. The Zinc Interface Library is a C++ user interface library from Zinc Software
  31857. Inc. for PC compatible computers. It supports MS-DOS text mode, MS-DOS
  31858. graphics mode and MS-Windows 3.x interfaces. I'm reporting on Zinc Version 2.0
  31859. for Borland C++. Zinc also supports the Zortech C++ complier.
  31860. C programmers moving to C++ and experienced C++ programmers will find the Zinc
  31861. Interface Library helpful. I've been using it for over a year on projects I've
  31862. been working on at home.
  31863.  
  31864.  
  31865. Features
  31866.  
  31867.  
  31868. Zinc's primary feature is a C++ class library. It consists of class
  31869. definitions, object code and optionally, source code for those classes. The
  31870. class library is designed so that your application only needs to have one set
  31871. of source code for writing applications in text mode for MS-DOS, graphics mode
  31872. for MS-DOS (CGA, EGA, VGA, Hercules compatible) and MS-Windows 3.x.
  31873. The user interface created in all three modes is SAA compliant. This means it
  31874. has windows, menus, dialogue boxes, list boxes, optional mouse support, and
  31875. all the other features users have come to expect in modern software.
  31876. All three modes, text, graphics and MS-Windows have the same basic look and
  31877. feel. The UI_DISPLAY class encapsulates the three display types in its
  31878. descendants. The UI_DISPLAY class defines all the things that a program can do
  31879. to a user's display.
  31880. Text mode applications use the PC extended ASCII graphics set for windows. The
  31881. programmer has several options when using text mode:
  31882. 1. The application can automatically detect the current text mode and use it.
  31883. This is the default.
  31884. 2. The programmer can explicitly use 25x80, 25x40 or 43x80 (which gives 50x80
  31885. on VGA).
  31886. 3. The programmer can give the user a choice on which mode to use.
  31887. Graphics mode MS-DOS display classes use complier specific libraries. The
  31888. library I have uses BGI (Borland Graphic Interface). The documentation
  31889. indicates that there is similar support for Zortech's graphic library. An
  31890. application can switch from graphics to text mode and back without loosing the
  31891. information in the user's windows. Besides the text mode features, the
  31892. graphics display classes have support for graphic specific features like arcs,
  31893. polygons, bitmaps, etc.
  31894. While one MS-DOS application can support both text and graphics, you must
  31895. recompile your program to support MS-Windows. You also must add a couple of
  31896. #ifdef statements to call WinMain instead of main, and to redefine how colors
  31897. are used. Other than that, all my MS-DOS applications, including graphic
  31898. applications, ran without modification on MS-Windows.
  31899. The strong points of the class library far out-weigh the weak points. Because
  31900. the class library is set up in a very logical hierarchy, it is easy to learn.
  31901. The field validation capability is one of my favorite features. All user input
  31902. can be validated by the program. Most common validations are included and the
  31903. programmer can define his own easily.
  31904. The library is fairly robust. Most of the errors I found in my programs were
  31905. my own. When they weren't there was usually a fix on their BBS. The library is
  31906. complete and generally well designed.
  31907. The only weak points that I found were in the MS-Windows mode. The first is
  31908. that since it always displays its bitmaps one pixel at a time, bitmap displays
  31909. in MS-Windows are very slow.
  31910. When I tried to work around this by using Windows bitmaps, I discovered that
  31911. there is no obvious way to use Windows resources with Zinc. Zinc has its own
  31912. version of resources, and the BBS contains some programs to convert Windows
  31913. bitmaps to Zinc bitmaps and Windows icons to Zinc icons.
  31914. Finally, Zinc doesn't have any direct support for MS-Windows Multiple Document
  31915. Interface (MDI). MDI applications keep all the child windows of a main window
  31916. confined within the boundary of the main window. Some programmer's will see
  31917. this as a bigger problem than others.
  31918. The other major feature that comes with Zinc is the Zinc Designer. The Zinc
  31919. Designer is a program that allows the programmer to draw windows, dialogues,
  31920. menus, bitmaps etc. Using the Designer is faster than writing the equivalent
  31921. source code. It allows the programmer to make better "Look and Feel"
  31922. decisions.
  31923. I found several weak points with the Designer. Some of the features that Zinc
  31924. supports, like radio buttons and check boxes, are not supported by the
  31925. Designer. The menu items in the Designer are cumbersome to edit. My final
  31926. complaint is that the Designer generates only binary objects, not source code.
  31927. I would like to see both.
  31928.  
  31929.  
  31930. Documentation
  31931.  
  31932.  
  31933. The Zinc documentation consists of three books: The Programmer's Guide, The
  31934. Programmer's Tutorial, and The Programmer's Reference. It also includes a
  31935. Quick Reference Guide containing a list of the most common constructors,
  31936. flags, event information, and a class hierarchy.
  31937. The Programmer's Guide provides a good overview of the concepts in the Zinc
  31938. Interface Library. It is a short book that hits the most important points of
  31939. the library. It also contains a user's guide to the Zinc Designer.
  31940. The Programmer's Tutorial is a clear and simple book to help the programmer
  31941. get started. It is short enough to stay interesting. That is a major
  31942. accomplishment when you consider that it not only contains lessons on using
  31943. the Zinc Library, but a C++ and object-oriented design tutorial as well. The
  31944. examples are excellent. They are clear and many are useful building blocks for
  31945. your own applications.
  31946. The Programmer's Reference is a well organized book that covers most of the
  31947. classes that come with Zinc. It could be more complete in its handling of
  31948. specific classes. For example, the width parameter in the Line method of
  31949. UI_DOS_BGI_DISPLAY is ignored. I spent about two hours trying to find the bug
  31950. in my code before I checked with technical support. They agreed that it should
  31951. have been documented.
  31952.  
  31953.  
  31954. Support
  31955.  
  31956.  
  31957. I have found Zinc technical support to be absolutely terrific. I usually use
  31958. the Zinc BBS for support. It is well organized and well maintained. It
  31959. contains corrections, news, user contributions and additional examples. The
  31960. message base provides contact with the people at Zinc and with other users. I
  31961. usually search the message base and find the answer I'm looking for without
  31962. having to post a question.
  31963. When I can't wait for the BBS I call Zinc's voice line. I've never had a long
  31964. wait when I've called. The support people are very friendly, helpful and
  31965. knowledgeable. They usually understood my problem better than I did and had
  31966. helpful suggestions in addition to the answers to my questions.
  31967.  
  31968.  
  31969. Competition
  31970.  
  31971.  
  31972.  
  31973. Borland's ObjectWindows and Turbo Vision are the other C++ user interface
  31974. libraries I am familiar with. Borland chose to use separate class hierarchies
  31975. for MS-DOS text and MS-Windows user interface. That means that if you want to
  31976. write one application for both MS-DOS and Windows, you have to write two
  31977. separate programs.
  31978. ObjectWindows has more direct support for MS-Windows than Zinc. It includes
  31979. classes that can use Windows resources, for example. But, it lacks the field
  31980. specific editing features, like dates, that Zinc has.
  31981. Turbo Vision is a text mode user interface class library. It has features Zinc
  31982. doesn't, like the THistory class that allows the user to keep a list of data
  31983. entry choices. It also lacks certain features, like the extensive field
  31984. support, that Zinc has. Turbo Vision also lacks the MS-DOS graphics support
  31985. that Zinc has.
  31986. Generally, if you're doing strictly MS-Windows programming, Borland's OWL has
  31987. a slight edge because it is strictly for Windows. Its bitmaps are displayed
  31988. faster, it can use resources from Borland's Resource Workshop (or other
  31989. sources) and there is full MDI support.
  31990. If you want to write strictly text mode programs, Zinc has a slight edge over
  31991. Turbo Vision because it has more support for formatted and validated data
  31992. entry fields. If you want to write MS-DOS graphics mode programs, Zinc wins
  31993. hands down because neither Turbo Vision or OWL supports that mode.
  31994. If you want to write applications that can be used across all three platforms,
  31995. I recommend Zinc because you'll be doing less rewriting of code. The Zinc
  31996. library fulfills the promise of code reuseablity much better than either
  31997. ObjectWindows or Turbo Vision.
  31998.  
  31999.  
  32000. Conclusion
  32001.  
  32002.  
  32003. The Zinc Interface Library is a good value. There is no doubt that Zinc will
  32004. save time for C++ programmers who write programs for the PC. It lets you
  32005. program for three PC platforms in the time it usually takes to write code for
  32006. one. The examples of C++ code will be of great help to the new C++ programmer.
  32007. The library is an example of good Object Oriented Design.
  32008. I have only two suggestions for improvements. I would like to see a more
  32009. complete reference manual. I also would like to see the Windows library have
  32010. more support for the features of Windows 3.x.
  32011. I would recommend the Zinc Interface Library to any C++ programmer who wants
  32012. to write applications for the PC.
  32013.  
  32014.  
  32015. Editor's Note:
  32016.  
  32017.  
  32018. As this issue went to press, we were informed that Zinc Interface Library v3.0
  32019. is now available. According to a Zinc representative, version 3.0 addresses
  32020. some of the problems noted in this User Report. New features include direct
  32021. use of Windows bitmap functions; MDI support for Windows and DOS; new window
  32022. objects such as toolbar, combo box, checkbox, radio button, and buttons with
  32023. associated bitmaps; Zinc Designer adds a toolbar and access to all library
  32024. features including user functions and validation routines. For more
  32025. information contact Zinc Software Incorporated, 405 South 100 St. Suite 201,
  32026. Pleasant Grove, UT 84062, (801) 785-8900, FAX (801) 785-8996.
  32027.  
  32028.  
  32029.  
  32030.  
  32031.  
  32032.  
  32033.  
  32034.  
  32035.  
  32036.  
  32037.  
  32038.  
  32039.  
  32040.  
  32041.  
  32042.  
  32043.  
  32044.  
  32045.  
  32046.  
  32047.  
  32048.  
  32049.  
  32050.  
  32051.  
  32052.  
  32053.  
  32054.  
  32055.  
  32056.  
  32057.  
  32058.  
  32059.  
  32060.  
  32061.  
  32062.  
  32063.  
  32064.  
  32065.  
  32066.  
  32067.  
  32068. C Express: 250+ Ready-To-Run Assembly-Language Routines for Turbo C, Microsoft
  32069. C, and QuickC
  32070.  
  32071.  
  32072. Stephen Patten
  32073.  
  32074.  
  32075. Stephen Patten is a senior analyst with the Lincoln Savings Bank in New York
  32076. and teaches C at New York University. He can be reached at (516) 932-3484.
  32077.  
  32078.  
  32079. C Express: 250+ Ready-To-Run Assembly-Language Routines for Turbo C, Microsoft
  32080. C and QuickC, a book and accompanying diskettes, was written (both the
  32081. assembly source code and text) by Robert Jourdain and published by Brady
  32082. Books, a division of Simon & Schuster, Inc. The copy I reviewed was dated
  32083. 1989.
  32084.  
  32085.  
  32086. Companion Diskettes
  32087.  
  32088.  
  32089. Two 5.25" diskettes accompany the 412-page book. One diskette contains the
  32090. eight library files which house the 250 plus routines in object code form
  32091. along with eight parallel header files for function prototyping. The other
  32092. diskette contains the assembly language source of the routines in compressed
  32093. form. (You can exchange the 5.25" disks for 3.5").
  32094. You install the libraries and header files on a hard drive by a simple copy,
  32095. and then call routines as you would any C function inside source code. A
  32096. global variable is also set which is used by the compiler to configure the
  32097. correct memory model.
  32098.  
  32099.  
  32100. Book Contents
  32101.  
  32102.  
  32103. Chapter 1 provides a short introduction to using the library. Chapters 2-9
  32104. document the library files included on the companion disk. Chapter 10 includes
  32105. a discussion on writing your own assembly subroutines to link with C programs.
  32106. This is followed by indices of general topics, the routines, and their object
  32107. files.
  32108. The routines or functions are documented pretty much as you would expect, each
  32109. described in terms of its purpose, parameters, globals required, kinds of
  32110. errors checked, and relevant peculiarities. Examples of calls are also amply
  32111. provided.
  32112. The book also presents background information on groups of routines. For
  32113. example, functions that implement expanded memory are preceded by a
  32114. description of the LIM specification, not in great detail, but enough for the
  32115. programmer to understand what the routines do and why.
  32116. Interestingly, video routines which can be implemented in either ROM BIOS or
  32117. memory-mapped form are presented both ways, with the ROM BIOS routines
  32118. suffixed with an underscore and lower case b. Depending upon the application,
  32119. the programmer can choose between speed and portability.
  32120.  
  32121.  
  32122. Overview of Functions
  32123.  
  32124.  
  32125. The equipment configuration routines access the usual details of installed
  32126. hardware and are only notable in that they eliminate the bit manipulations
  32127. required when using the compiler library to extract the same information.
  32128. The memory management routines test for extended and expanded memory, and
  32129. allow programming of expanded memory applications. Pages can be allocated,
  32130. switched and deallocated, and data exchanged between expanded memory and RAM
  32131. quickly and easily. This can be a real plus to a C programmer restricted by
  32132. the one megabyte limitation of the MS-DOS environment.
  32133. C Express routines, like hex_to_char, hex_to_int, binary_to_char, and
  32134. binary_to_int, interpret such strings as numbers, producing values which can
  32135. then be entered as numeric data in C programs. Combined with existing stdlib.h
  32136. utility functions, like itoa, they can also be used to convert binary to hex,
  32137. or vice versa, a nice library facility.
  32138. There are string manipulation routines to add or delete characters to or from
  32139. strings, change string characters, and perform various substring operations.
  32140. There are also keyboard routines that provide for fast operating system calls
  32141. to check, read, clear, wait on and change characters, and scan codes in the
  32142. keyboard buffer.
  32143. filter_in, for example, captures the next keystroke in the buffer then looks
  32144. for the same character in a predefined table. If found, the keystroke is
  32145. accepted; otherwise, it's rejected. A related routine, filter_out, does the
  32146. opposite, rejecting a character found in the table.
  32147. key_pause allows a key to act like an ON-OFF switch. Hold it down and, let's
  32148. say, a help screen appears. Release it, and the help screen disappears. This
  32149. can replace long and complicated if and switch statements with cleaner, faster
  32150. code.
  32151. Mouse routines, based on Microsoft's driver, position the mouse cursor, turn
  32152. it on and off, capture a button press, report mouse positioning and control
  32153. mouse motion to cursor movement. With pixel_ratio, you can set the exact
  32154. movement ratio between the mouse and its cursor motion. With
  32155. define_graphics_cursor, you can set the precise pixel or "hot spot" inside the
  32156. pattern of pixels th