home *** CD-ROM | disk | FTP | other *** search
- **** This text is extracted from the volume 7.4. ****
-
- Porting Microplox To XENIX
-
- Microplox (CUG266) is a graph drawing program that works under CP/M
- or MS-DOS using an Epson FX-80 printer (or in our case, a laser printer
- which can emulate the Epson). Recently we adapted the MS-DOS original
- to work under XENIX. In a technical sense the incompatibilities were
- relatively mild and commonplace (BIOS I/O calls had to be mapped to
- XENIX I/O capabilities and int/pointer puns had to be eliminated),
- but locating and understanding these problems was somewhat complicated
- by Microplox's unusual coding and design style.
-
- The Implementation
-
- Microplox consists of two programs:plox translates a graph
- specification into a file of reasonably abstract plotting commands
- (PLOXCOM.DAT) and ploteps builds and prints an Epson
- compatible bit map from the plotting commands.
-
- ploteps is conventional in design and coding, but plox
- is (at least at the top level) an unusual collection of object-like
- functions, one for each different type of statement in the Microplox
- specification grammar. These object-like functions have uniform message-passing
- interfaces which require a single text string (messages) as input. Each
- message consists of a series of keyword-value pairs. The function
- parses the keyword to select an appropriate method for processing
- the associated value. Thus a specification file can be parsed merely
- by repackaging each statement as a message and sending it to the appropriate
- object function.
-
- Each of these object functions also owns certain private (static)
- data. Low level messages (those with strictly local effect) result
- either in changes to this private data or immediate output of a plotting
- command or both. Higher level messages (those that affect non-local
- data), however, may require changes to the private data of several
- distinct object functions. Object functions effect changes in the
- private data of other object functions by constructing a message and
- sending it via the function SendSpec().
-
- SendSpec() requires three arguments: a string representing
- the method part of the message, a value, and a pointer to the target
- object function. SendSpec() formats the value as a string,
- appends it to the method and invokes (via the pointer) the target
- function with the resulting message as the single input parameter
- (see Listing 1).
-
- /* Send a "keyword value" string to a given function */
-
- void SendSpec (KeyWord, Value, Process)
- char *KeyWord;
- int Value, (*Process)();
- {
- char Digits[7], Spec[20];
-
- strcpy (Spec, KeyWord);
- strcat (Spec, " ");
- sprintf (Digits, "%d", Value);
- strcat (Spec, Digits);
- (*Process) (Spec);
- }
-
- (Listing 1)
-
- The second paramter, Value, is declared as a simple int. Unfortunately,
- not all methods require a simple int as their value. Some
- require strings, others need several values. In classic C tradition,
- SendSpec() ignores these differences, assuming that int
- is actually a universal type. Both pointers and ints are passed
- as values. SendSpec() converts each to an ASCII string,
- as if an int had been received, and the receiving function
- must examine the method and use the received value correctly.
-
- This type pun works fine as long as ints and pointers
- are the same size and pointers can be reliably converted to and from
- an integer representation. But, when pointers are 32 bits and ints
- 16 (as in our target XENIX environment), the result is a program that
- compiles without warning but immediately dumps core when executed.
-
- We considered several alternative repairs. We could merely have lengthened
- Value to 32 bits (cast ints to long at each call). With
- appropriate typedefs and a few coding changes, we could have
- made it easy for others to readjust the variable sizes to match other
- machines. But what about environments where pointers don't have a
- meaningful or unique integer representation? We decided there was
- no certain way to sprintf() a pointer to a character string
- and still be able to convert it back.
-
- We also considered passing through a union, but that seemed to complicate
- both the sending and receiving code at least as much as the obvious
- alternative: adding a second field to the basic message.
-
- The New Message
-
- As originally coded, all plox messages were a single string,
- possibly with multiple method pairs. We modified that so that messages
- were a string and a possibly NULL pointer value (see Listing
- 2). Most methods are still paired with their values in the message
- string, but methods requiring a pointer value retreive it from the
- pointer parameter instead.
-
- void SendSpec (KeyWord, Value, Scaleptr, Process)
- char *KeyWord;
- int Value, (*Process)();
- SCALING *Scalepr;
- {
- char Digits[7], Spec[20];
-
- strcpy (Spec, KeyWord);
- strcat (Spec, " ");
- if (Value != 0) {
- sprintf (Digits, "%d", Value);
- strcat (Spec, Digits);
- }
- (*Process) (Spec, Scaleptr);
- }
-
- (Listing 2)
-
- We considered putting both parts of the message in a structure, but
- that approach would have required significant changes throughout the
- program in how variables are declared and used. Adding a parameter
- required changes only to the calls to SendSpec(),the calling
- interfaces of the object functions, and the few methods which expect
- pointer values. Passing what is conceptually a single message as
- two distinct parameters lacks elegance, but works and seems to do
- less injury to the code than the alternatives.
-
- The Printer Driver
-
- ploteps is responsible for creating a graphic image and sending
- it to the printer. In the orginal program, the image was output via
- calls to BDOS functions a single-user approach not feasible
- under XENIX. We considered two alternatives: replacing the low-level
- MS-DOS I/O calls with comparable low-level XENIX/UNIX I/O calls, and
- restructuring the I/O to take advantage of UNIX streams through high-level
- buffered I/O commands.
-
- The first method is straightforward. The program opens the printer
- device file and writes the graphic image directly to the printer.
- System functions open(), read(), write(), ioctl() and close()
- will have to be used in the program. The second method is much more
- flexible, easier to debug, and doesn't interfere with sharing the
- printer among several users. Under the second method, the program
- generates a binary temporary file that holds a graphic image. This
- binary file is sent to the print spooler using the lp or lpr
- commands (Figure 1).
-
- ------- --------- ----
- | plox | --> PLOTCOM.DAT --> | ploteps | --> IMAGE ---> | lp |
- ------- --------- ----
- |
- V
- ---------------------
- | printer | device |
- (Figure 1) | interface | driver |
- ---------------------
-
- Unfortunately the stock printer interface performs certain post-processing
- on text files. At a minimum newlines (linefeeds) are converted to
- CR-LF pairs. The binary file includes escape sequences to put the
- printer in graphics mode so that it will interpret a binary 0x0A
- as a dot pattern, but the interface and printer device driver don't
- recognize the graphics mode, and continue to expand linefeeds.
- To circumvent this problem, we modified the interface program (a piece
- of shell script) by adding a local binary option.
-
- Patching The Interface
-
- Each printer device under UNIX/XENIX has its own printer interface
- program at the subdirectory, /usr/spool/lp/interface. The printer
- interface program is a piece of shell script that is invoked each
- time lp is executed with the target printer. The task of the
- interface program is to configure the device driver (via stty)
- to properly handle the printer, display a printer request message
- to the monitor, and forward the contents of the print image to the
- device handler.
-
- In order to send the Microplox graphic image to the target printer
- without interference from the interface or device driver, you must:
-
- 1. Disable the newline ranslation.
-
- When printing plain ASCII text, the stty mode onlcr
- is always set so that a newline character in the text is translated
- into a combination of a carriage return character (0x0D) and
- a line feed character (0x0A). However, when printing a graphic
- image, this translation should be disabled.
-
- 2. Change the printer emulation mode.
-
- The escape sequence must be sent to the printer prior to sending the
- contents of a graphic image file. The emulation mode should be set
- to Epson FX-80. (Normally we keep our laser printer in HP+ mode.)
-
- 3. Reset the printer.
-
- On exit, the escape sequence to reset the printer must be sent.
-
- These modifications should be triggered only when the binary option
- has been requested (a -o flag on the command line). For example,
- to print a graphics image file, graf, the command line is:
-
-
- lp -oepson graf
-
- The stock printer interface includes the basic framework for recognizing
- -o flags and binding the following parameter to the options
- macro, making it easy to add local options.
-
- Note that this change is general purpose. We could have overcome these
- problems by directly controlling the printer, but we would have solved
- the problem for a single program. Now anytime we need Epson compatibility,
- we can simple spool the output with the -oepson option.
- The modified printer driver is shown Listing 3.
-
- printer=`basename $0`
- request=$1
- name=$2
- ntitle=$3
- copies=$4
- options=$5
- shift; shift; shift; shift; shift;
-
- if [ "$options" = epson ]
- then
- stty -onlcr 0<&1 # Disable newline char translation
- echo "!R! FPRO P1, 5; EXIT;" # Emulate Epson FX-80
- else
- stty onlcr 0<&1
- fi
-
- while [ "$copies" -gt 0 ]
- do
- for file
- do
- cat "$file" 2>&1
- echo "\f\c"
- done
- copies=`expr $copies -1`
- done
- echo "!R! FRPO P1, 6;EXIT;" # Reset the printer
- }
- exit 0
-
- (Listing 3)
-
- If you have modified the printer interface script directly, to activate
- it you need only disable and then enable the printer. If you have
- instead modified the interface model (see the manual) you should run
- lpinstall which will diable the printer, copy the model script
- to the appropriate printer interface file, and then enable the printer.
-
- We have incorporated our adaptations into the release master for Microplox
- (CUG266), which can now be compiled under CP/M, MS-DOS, and UNIX/XENIX
- operating systems.
-
- Conclusion
-
- Microplox is an interesting, but not quite convincing, experiment
- in object style coding and design. The choice of objects makes parsing
- elegant (at least at the most abstract levels of the design), but
- requires kludges (in the form of special purpose messages between
- objects) to effect the necessary changes to graph modalities. Basically
- the statements of the specification language aren't sufficiently orthogonal
- to double as well designed messages to well chosen objects. Given
- the need to parse this language, it seemed to us that separating the
- parsing from the graph production would have produced a more satisfying
- design.
-
- Second,
- SendSpec("SYLEN", Xlen, AXcon);
- doesn't have any obvious advantages over
- AXcon(build_msg("XYLEN",Xlen));
- since both require the name of the function to be known
- at compile time. SendSpec() could, however, become much more
- powerful if it examined the message for the target function name and
- extracted the appropriate pointer from a table.
-
- Design issues aside, Microplox has proven a useful tool. It is reasonably
- portable (even more so now), and produces some very usable graphs.
- If you need a simple graphing utility, you may want to investigate
- this volume.
-
-
-