Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Tools

Writing Portable Applications With X/GEM


MAR89: WRITING PORTABLE APPLICATIONS WITH X/GEM

WRITING PORTABLE APPLICATIONS WITH X/GEM

How does X/GEM compare with other windowing systems?

Bill Fitler

Bill Fitler is the X/GEM project manager with Digital Research (DRI) in Monterey, Calif. He can be reached at 408-649-3896.


What are the main issues in creating portable graphics applications? For example, how do you implement foreign language versions of your software? And how do you handle the fundamental differences among various hardware platforms? To achieve portability, you need to follow certain general guidelines. The rules depend partly on what your application does, and partly on what you want to port to. This article explores these and other problems, showing how it's done with X/GEM, and comparing the X/GEM solution with other common windowing systems.

Let's begin by discussing what X/GEM is.

X/GEM is based on the older Graphical Environment Manager (GEM), introduced in 1984 by Digital Research (DRI). GEM enables graphics applications to run on a broad range of computers, ranging from Ataris to low-end 8088 based PCs and compatibles. X/GEM provides a GEM compatible application program interface (API) that functions on higher-end workstations, thus allowing GEM programs to be more easily ported to these more powerful computers. X/GEM currently works with DRI's FlexOS (a multitasking, multiuser, real-time operating system), and versions are in the works for Presentation Manager and the X Window system.

The X/GEM API provides two sets of run-time application services, shown in Figure 1. The first, called the virtual device interface (VDI), is where graphics portability is implemented. The second is the application environment services (AES), which provides the user interface building blocks for an application. Built on top of the VDI, the AES also provides functions, that enhance portability to both single tasking and multitasking operating environments. X/GEM doesn't provide file system functions. Instead, GEM applications rely on the services provided by the underlying operating system.

This basic structure of the X/GEM API --a user-interface-function set resting atop device independent graphics -- is similar to Windows and the Macintosh. All three systems implement a user interface "policy:" that is, the basic look and feel shared by all applications running in that windowing environment.

X Window takes a different approach. Its implementors went to great lengths to provide a "policy-free" interface. For example, the Xlib interface does not contain any window control mechanisms. Instead it abdicates this policy to an X "window manager" responsible for providing the look and feel of the interface. This design has merits, because all X applications can be made to behave in a fashion deemed appropriate by the window manager. At MIT, before X was developed, there were many different vendors supplying software and hardware, leading to a Tower of Babel effect. Now for applications that use X, a person needs to learn only one window manager in order to use an application from any vendor. This also allows new ways of interacting with the user to be developed, which is useful because the art of user-interface design is still in its infancy.

The X Window philosophy, however, also has drawbacks. One is increased application complexity due to the need to negotiate with a window manager which, for example, may not allow a window to be arbitrarily located on the display device. A second problem is that this design requires more computing resources. That might not be an issue in a workstation environment, but it is on a low-end personal computer.

Another X Window problem is the proliferation of window managers, each of which sets forth its own user-interface policy. As of this writing no clear winner has emerged, with the result that end users are confused about which window manager to select. The Open Software Foundation (OSF) hopes to end the dilemma by choosing one as the X Window user-interface standard. A number of vendors have proposed their solutions to OSF, including DRI with X/GEM. Whatever the outcome, end users could benefit by having a single style of user interface across the entire range of supported applications and computers. Another beneficiary is programmers, who will then be able to write portable applications for a consistent environment with few worries about hardware and operating system differences.

Now let's consider some specific issues that arise when you want to make your graphics application portable.

Dealing with Graphics Devices

Windowing systems such as Presentation Manager and X Window provide a set of functions that draw to the screen as independently of the actual physical device as possible. These functions also return information about the device so that the program can deal with specifics and idiosyncrasies consistently.

In X/GEM, the VDI handles graphics-device portability. The VDI is built around a conceptual graphics model called a workstation (not to be confused with those powerful high-end personal computers going by the same name). X/GEM VDI workstations are specific graphical devices available to an application. Thus, the display and the printer are both VDI workstations. When an application opens a workstation, the appropriate graphical device driver is loaded. The device driver translates high-level VDI commands into the low-level actions necessary to produce graphical information on the target device or to receive information from it.

This approach is similar to that taken by other windowing systems. In Windows the approximate equivalent is called a display context, on the Macintosh it is called a grafPort, and in Xlib it is called a graphics context.

X/GEM furnishes multiple classes of workstations. These include screens, plotters, printers, cameras, scanners, and metafiles (files containing sets of graphic commands). In general, the application can send a given set of commands to the screen and then issue the same set of commands to the printer or metafile device to create a more enduring copy of the output. Graphic systems such as the Macintosh and X don't have the same kind of generality built into their graphics models, choosing instead to support primarily the screen display device.

One of the most important issues in writing portable graphics applications concerns the concept of uniform rendering. Graphical devices span a wide range of addressability and resolution. Many displays for PCs --the Macintosh being a notable exception --use pixels that are not square. This means that a square box with ten units of measurement in height and width will show up as a rectangle. The ratio of height to width is called the aspect ratio of the device.

Microsoft Windows handles the aspect ratio by providing some half-dozen functions to scale coordinates for a display context. The application programmer selects from coordinate systems that are metric, English, arbitrary, or raster dependent. Each coordinate is scaled automatically at output time.

In contrast, the X/GEM VDI uses primarily raster coordinates. This means the application must do its own scaling. The GEM designers felt that the application is best able to decide how to do the scaling in the most efficient manner. For example, some CAD applications need to scale in floating point, whereas many publishing applications can use integer or fixed point scaling.

The X/GEM VDI provides aspect ratio information so that the application can handle coordinate translation in the most appropriate manner. The height and width of a pixel (in microns) is returned by the VDI call that opens the workstation. Listing One, page 92, illustrates how this is done with a simple integer scaling function.

Speaking in Foreign Tongues

As new markets open, it becomes increasingly important to be able to translate applications into other languages. Doing this easily requires a strategy other than the traditional use of embedded text literals in the source code.

With X/GEM, foreign language portability can be achieved by placing all the application's text messages in resource files. One of the system's most important portability feature is the AES function for object and resource handling. The GEM Programmer's Tool to build and edit these resources is called the resource construction set or RCS.

RCS provides tools for constructing and displaying menus, windows, and dialog boxes in files independent of the application code. Each object has a position and size, along with type and data information. The objects are organized hierarchically, thus allowing them to be defined, displayed, and manipulated relative to each other. Examples include icons, text, boxed text, boxes, editable text fields, and programmer-defined objects.

The collection of objects for a given application is grouped together in a resource file. When a GEM application initializes itself at run time, it calls the AES to load the resource file. The resource file contains all the text that the user sees, separate from the executable code. This allows a programmer to prepare a resource file for each supported foreign language without recompiling the application.

One warning: If you intend to translate menu and dialog items, leave plenty of extra visual space in the enclosing boxes for the translated messages. For example, translating from English to German usually requires between 50 and 100 percent more characters per message.

The RCS also allows an application designer to get quick feedback about the user interface, even before a large amount of code is written. Instead of using the application's programming language, you use the interactive RCS tools to specify exactly what gets placed where. Thus, for example, you can use RCS to place a string object in a dialog box, change the text as necessary, reposition and tweak it, and then label the object for access by the application. The difference between constructing a moderately complex dialog with the RCS versus a compiled language is incredible.

The RCS produces a resource file. It can also produce a file format suitable for compiling directly into the program. You might do this for a variety of reasons, such as protecting the resource information or reducing distribution complexity by eliminating the resource file altogether. The penalty for doing so is loss of portability to some extent.

The resource file shown in Listing Two, page 92, contains a single dialog box with two button objects. Without going into detail about the construction of a resource, there are a few things to note. First, it took less than three minutes from start to finish to create the dialog. Figure 2 shows the RCS display during the design. Next, the structure of the resource file is well defined and therefore described by the first few defines, which give critical array sizes. Finally, the RCS generates an include file with the contents:

  #define SAMPLDLG 0     /* TREE*/
  #define BTNCANCL 4     /* OBJECT in TREE #0 */
  #define BTNOK 3        /* OBJECT in TREE #0 */

The #defines give indices into the resource array so that the application can refer to specific objects. These data objects change values according to user input, thus allowing the application to determine exactly what the user entered.

What about the OS?

The capabilities and characteristics of various operating systems (DOS vs FlexOS vs Unix vs OS/2) also influence strategy in writing portable applications. Two critical areas are file and event handling. Many GEM applications call directly the native operating system's file-handling primitives. This is done primarily to avoid the overhead of C run-time libraries. Another reason for avoiding the run-time libraries is that they usually build in bulky support to handle screen output, such as printf( ). In general, applications running under a windowing manager such as GEM and Windows cannot use the operating system's screen and keyboard I/O services at all; all I/O must go through the environment manager.

An application targeted toward several operating systems needs a file system interface that can be readily ported to any of them. One way to deal with this problem is to use the standard routines (that is, fopen (), fclose (), fread (),fwrite (), and so on) provided with the C run-time library, and pay the price in overhead for portability. Another way is to emulate the file system calls of one operating system on another. For example, DRI furnishes a DOS emulation run-time library for the X/GEM product on FlexOS.

The AES provides a degree of operating system independence by providing a file-selector mechanism enabling an application to solicit a file specification from the end user. This lets the user navigate through the file system and change directories as necessary to find or specify the desired file. DRI plans to tailor the mechanism for each supported operating system in order to hide the subtleties of file system navigation from the user.

The AES also provides an event driven input model with which applications can work effectively in a broad range of environments. The application waits for multiple events, where an event could be a keypress, a mouse button press, a timer tick, or certain kinds of messages. These messages include notifications that the user has requested a menu item, or changed the size or position of a window. A message can also be information from other applications. The point is that the application doesn't have to actively poll the environment looking for something to happen. Instead it suspends activity until a specified event occurs. This greatly enhances portability to multitasking environments; the application becomes a "good neighbor" in the computing environment by not wasting unnecessary CPU resources.

The X/GEM event structure in Listing Three, page 92, looks complicated, but it's easy to understand. The program fills in the e_flags field with a bitmask containing those events that it wants to wait for. When a bit for a specific event is turned on, the structure element controlling that event must be filled in. In the example shown in Listing Four, page 92, the program waits for one of two things to happen: either a keypress or an elapsed time of ten seconds. The application can also wait for a button press, a message, or for the mouse to enter or leave one of two rectangular regions on the screen.

In Windows and in X, the application notifies the system that it will wait for a number of different types of messages. This is roughly equivalent to the evnt_event() mechanism of GEM, except that events are serialized because the application can only receive a single message at a time. The Macintosh has a much cruder mechanism, where it cycles through a loop, polling certain functions to see if anything has happened. This works fine when a single application is running on the machine, but it causes conflicts and excessive overhead when multiple applications are polling at the same time.

Moving Among Different Processors

The wide differences among microprocessor architectures demand a careful strategy where portability is in the picture. The main requirements are programming in a portable language such as C, along with careful attention to, and independence of, byte ordering, integer size, and correct and consistent use of pointers. These and other concerns were discussed in Greg Black-ham's article "Building Software for Portability" (DDJ, December 1988).

With respect to X/GEM programs, there is one additional set of portability issues. In order to minimize the amount of code in GEM applications, the X/ GEM bindings are defined to use the mixed model programming applicable to the segmented Intel architectures. To simplify, an application can use either long pointers --access to the entire address space at the expense of code compactness --or short pointers, which decrease executable program size but can only address up to 64K from a fixed location. GEM applications are mostly in small models (short pointers) with certain kinds of pointers being kept in long format. Although this is an extra burden to the application programmer, it can be readily handled by the strict parameter type checking available in most up-to-date C compilers.

Writing portable applications takes a different design philosophy and tool set than developing for a single system. There's nothing mysterious about the process; it's just different, requiring observance of some common sense rules. A well-crafted user-interface subsystem such as X/GEM, which was specifically created for multiplatform, multi-device portability, can also greatly ease the headaches of moving software from one environment to another that differs radically.

_WRITING PORTABLE APPLICATIONS WITH X/GEM_ by Bill Fitler

[LISTING ONE]

<a name="008d_0009">

          /* GEM uses 16 bit quantities for raster
             coordinates */
      typedef short int WORD;
      WORD pxl_width, pxl_height;
      WORD work_in[11], ws_handle, work_out[57];
      ...
          /* Open the workstation after filling in initial
             defaults into work_in[] */
      if( v_opnwk(work_in,&ws_handle,work_out) == FAILURE )
      {   /* Handle fatal error */ }
          /* After successful open, ws_handle identifies the
             desired workstation and work_out contains 57
             units of information about the device,
             including the width and height of each pixel in
             microns */
      pxl_width = work_out[3];
      pxl_height = work_out[4];
      ...
          /* Returns a value in y units that is scaled to x
             units */
      WORD scale_y(WORD raw_y)
      {
          return raw_y * pxl_width / pxl_height;
      }
      ...
          /* Draw a square on the screen that is "x_units"
             wide and looks square */
      VOID draw_square(WORD x, WORD y, WORD x_units)
      {
          WORD xy[10];
          xy[0] = x; xy[1] = y;
          xy[2] = x+x_units; xy[3] = y;
          xy[4] = x+x_units; xy[5] = y+scale_y(x_units);
          xy[6] = x; xy[7] = y+scale_y(x_units);
          xy[8] = x; xy[9] = y;
          v_pline(ws_handle,5,xy);
      }





<a name="008d_000a"><a name="008d_000a">
<a name="008d_000b">
[LISTING TWO]
<a name="008d_000b">

SAMPLE SOURCE OUTPUT FROM RESOURCE CONSTRUCTION SET


     #define T0OBJ 0
     #define FREEBB 1
     #define FREEIMG 1
     #define FREESTR 4

     BYTE *rs_strings[] = {
     "This is a Sample Dialog",
     "with an image and two buttons.",
     "OK",
     "Cancel"};

     WORD IMAG0[] = {
     0x7FF, 0xFFFF, 0xFF80, 0xC00,
     0x0, 0xC0, 0x183F, 0xF03F,
     0xF060, 0x187F, 0xF860, 0x1860,
     0x187F, 0xF860, 0x1860, 0x187F,
     0xF860, 0x1860, 0x187F, 0xF860,
     0x1860, 0x187F, 0xF860, 0x1860,
     0x187F, 0xF860, 0x1860, 0x187F,
     0xF860, 0x1860, 0x187F, 0xF860,
     0x1860, 0x187F, 0xF860, 0x1860,
     0x187F, 0xF860, 0x1860, 0x187F,
     0xF860, 0x1860, 0x183F, 0xF03F,
     0xF060, 0xC00, 0x0, 0xC0,
     0x7FF, 0xFFFF, 0xFF80, 0x0,
     0x0, 0x0, 0x3F30, 0xC787,
     0x8FE0, 0xC39, 0xCCCC, 0xCC00,
     0xC36, 0xCFCC, 0xF80, 0xC30,
     0xCCCD, 0xCC00, 0x3F30, 0xCCC7,
     0xCFE0, 0x0, 0x0, 0x0};

     LONG rs_frstr[] = {
     0};

     BITBLK rs_bitblk[] = {
     0L, 6, 24, 0, 0, 1};

     LONG rs_frimg[] = {
     0};

     ICONBLK rs_iconblk[] = {
     0};

     TEDINFO rs_tedinfo[] = {
     0};

     OBJECT rs_object[] = {
     -1, 1, 5, G_BOX, LASTOB, SHADOWED, 0x21100L, 0,0, 43,9,
     2, -1, -1, G_STRING, NONE, NORMAL, 0x0L, 10,4, 23,1,
     3, -1, -1, G_STRING, NONE, NORMAL, 0x1L, 7,5, 30,1,
     4, -1, -1, G_BUTTON, SELECTABLE, NORMAL, 0x2L, 10,7,
             8,1,
     5, -1, -1, G_BUTTON, SELECTABLE, NORMAL, 0x3L, 24,7,
             8,1,
     0, -1, -1, G_IMAGE, LASTOB, NORMAL, 0x0L, 19,1, 6,3};

     LONG rs_trindex[] = {
     0L};

     struct foobar {
          WORD dummy;
          WORD *image;
          } rs_imdope[] = {
     0, &IMAG0[0]};

     #define NUM_STRINGS 4
     #define NUM_FRSTR 0
     #define NUM_IMAGES 1
     #define NUM_BB 1
     #define NUM_FRIMG 0
     #define NUM_IB 0
     #define NUM_TI 0
     #define NUM_OBS 6
     #define NUM_TREE 1

     BYTE pname[] = "SAMPLE.RSC";





<a name="008d_000c"><a name="008d_000c">
<a name="008d_000d">
[LISTING THREE]
<a name="008d_000d">

/* mevent.h - define structure for GEM evnt_event() */

     typedef struct mevent
     {
     UWORD   e_flags;    /* events to wait on */
     UWORD   e_bclk;     /* num button clicks */
     UWORD   e_bmsk;     /* which mouse buttons */
     UWORD   e_bst; /* button up or down */
     UWORD   e_m1flags;  /* return on entry or exit */
     GRECT   e_m1;  /* rect 1 x,y,width,height */
     UWORD   e_m2flags;  /* return on entry or exit */
     GRECT   e_m2;  /* rect 2 x,y,width,height */
     LONG    e_mepbuf;   /* message buffer pointer */
     ULONG   e_time;     /* time to wait (ms) */
     WORD    e_mx;  /* return x */
     WORD    e_my;  /* return y */
     UWORD   e_mb;  /* return which buttons */
     UWORD   e_ks;  /* return kb state */
     UWORD   e_kr;  /* return kb code */
     UWORD   e_br;  /* return num button clicks */
     CHAR    e_reserved[24];  /* for system use */
     } MEVENT;

     WORD evnt_event( MEVENT * );






<a name="008d_000e"><a name="008d_000e">
<a name="008d_000f">
[LISTING FOUR]
<a name="008d_000f">

     MEVENT ev_str;
     ev_str.e_flags = E_KEYBD | E_TIMER;
     ev_str.e_time = 10000L;  /* in milliseconds */
     ret_flags = evnt_event( &ev_str );
     if( ret_flags & E_KEYBD )
     {
          /* The user pressed a key:
     Key code of key pressed returned in ev_str.e_kr;
     State of shift and control keys in ev_str.e_ks */
     }
     if( ret_flags & E_TIMER )
     {
          /* 10,000 milliseconds have elapsed since the call
             */
     }












Copyright © 1989, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.