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

The Portability Dream


MAR89: THE PORTABILITY DREAM

THE PORTABILITY DREAM

Now you can develop an application that will run under Windows and the Mac

Margaret Johnson

Margaret K. Johnson is a software engineer at Beckman Instruments. She can be reached at 2500 Harbor Blvd., M/S D-33-B, Fullerton, CA 92634. Compu-Serve ID: 74706,2325.


Some might say that the creation of portable code between windowing environments, such as Microsoft Windows and the Macintosh User Interface, is an impossible dream. After all, the architectures within which these environments operate are widely disparate. The Extensible Virtual Toolkit (XVT), however, is a package from the Advanced Programming Institute that promises to virtualize the code between these diverse environments to the point where the process of porting code between them should be just a matter of source transfer, creation of resources (such as menus, dialog boxes, and strings), and recompilation. XVT is a collection of C functions that attempts to abstract the major features of a windowing environment's application programming interface (API). XVT is a virtual toolkit, because it acts as the interface to the native toolkit. A user of this package stays away from direct calls to either the Windows Software Development Toolkit (SDK) or the Macintosh User Interface Toolbox.

XVT is currently available only for the Mac and Windows. Advanced Programming Institute is working on a version of XVT for the OS/2 Presentation Manager, and also promises a future version for X-Windows. Compilers supported by XVT include MS-C (Version 5.x) for Windows, and both Lightspeed C (Version 3.0) and MPW C (Version 2.0.2) on the Mac.

Those who stand to gain the most from XVT are developers who are familiar with C but are new to graphical user interface (GUI) programming, developers who don't want to invest the time and energy necessary in order to feel comfortable developing programs for either (or both) Windows or the Mac, and developers who don't require features that are unique to a particular API. The package contains enough built-in code to also benefit the developer who creates applications specific to the Mac or to Windows. XVT's built-in routines include functions for file handling, printing, managing the clipboard, font handling, dialog management, and a facility for easily including a help system within an application.

Portability Between Environments

To reach the dream of 100 percent portability, the process of virtualization cannot compromise too many features of an environment. The application determines which features can be compromised. Although the Mac and Windows are conceptually similar, there are enough differences in their implementations to make abstractions to some of their features difficult, if not impossible. Both APIs have terrific features that are specific to their respective environments. For example, Windows allows child windows, the sending and posting of intratask messages, dynamic link libraries, the dynamic data exchange for intertask messages, mapping modes (MM_LOENGLISH, MM_ANISOTROPIC, and so forth), window property lists, subclassing, and atom tables. Because Windows provides a nonpreemptive form of multitasking, its API contains functions that allow an application to yield to other Windows applications during processing.

On the other hand, the Mac supports more choices for menu styles, such as hierarchical, tear-off, and popup menus. It also allows nonrectangular and shadowed windows. In addition, the Mac's API contains functions that support internationalization and file handling dialogs; Windows does not include these functions. The differences between Windows and the Mac can be interpreted as constraints to the XVT API. With the exception of the standard file-open-and-save dialogs, the special features listed above for Windows and the Mac are not part of the XVT package. In addition, the version that I reviewed (1.2) does not include support for bitbltting (bit block transfers), the user of color, internationalization, RS-232 communications, sound, and mapping modes.

Handing Over Control

When I began reviewing XVT, I noticed that I took the side of what the product lacks, rather than what it has to offer. This perspective most likely results from both my long hours spent becoming familiar with the Windows SDK, and my current interest in Mac development. It's the perspective of a developer who is asked to move from a low-level language, such as assembler, to a higher-level language, such as C: Some control must be taken away. This is a hard thing to come to terms with. Once I gave XVT a chance, however, I was surprised at how many of the more important features are available.

These features include support for dialog boxes, windows (in the sense of Microsoft's Multiple Document Interface, as described in the MS-Window's SDK Application Style Guide), text, file handling, printing, drawing primitives, handling the clipboard, and 15 of what XVT's developers believe are the most popular events that can occur to a window. The dialog boxes can contain push buttons, radio buttons, check boxes, scroll bars, static text, edit boxes, and listbox controls. The drawing primitives allow the drawing of rectangles (with or without rounded corners), ovals, arcs, pie pieces, lines (with or without arrows), and polygons.

XVT's 15 Window-related events fall far short of the approximately 100 possible event messages that are available under Windows. Although the Mac also has 15 events, they're not identical (in meaning) to XVT's events. XVT's events are probably sufficient, however, for the 90 percent case. They include messages for mouse down, mouse up, mouse movement, mouse double click, keyboard input, window update, window activation/deactivation, window destruction, vertical scroll bar activity, horizontal scroll bar activity, menu commands, close window, resize window, selection from the font or style menu, and a request to quit the application.

Taking the Plunge

The XVT product that I received included a loose-leaf binder jampacked (almost to the point of explosion!) with pages including the "XVT Programmer's Manual," a question and answer sheet covering the most often asked questions, information on enhancements and changes since version 1.1, two 5 1/4-inch 360K floppy disks for the PC, two 3 1/2-inch 800K floppy disks for the Mac, and a registration form. The programmer's manual is divided into sections for installation and usage, a user's guide, a reference to each function (documented with a description, example, and list of associated functions), technical notes (giving insight on how to implement techniques not covered elsewhere), and a quick reference.

I felt the documentation did a good job of explaining both the objective of SVT and the functions that are implemented to reach that objective. The only major complaint is a lack of an index for each section. This is promised for a later release.

To use XVT on the PC, it's necessary to first install the Windows operating environment (2.x), the SDK (2.x), and Microsoft's C compiler (5.x). On the Mac side, I used Lightspeed 3.0. Note that all of this software assumes the presence of a hard disk.

Installation of XVT on the PC side is easy. First create the five subdirectories \XVT\BIN, \XVT\EXAMPLES, \XVT\INCLUDE, \XVT\LIB, and \XVT\SOURCE on the hard disk. Then copy the contents of the subdirectories on the XVT floppy to the corresponding subdirectories on the hard disk. The second floppy contains a program called XVTDraw that highlights the usage of XVT in a Windows draw package. It is for demonstration purposes only.

Once XVT is installed, life is easier if the lines that set the INCLUDE and LIB paths in AUTOEXEC.BAT are modified to allow the XVT paths. For example:

SET INCLUDE=\MSC\INCLUDE;\XVT
                     \INCLUDE
SET LIB=\MSC\LIB;\XVT\LIB

Note that all this discussion about paths assumes \XVT is the parent directory. If this isn't the case, a minor annoyance is encountered. The file XVTRSRC.HRC (located in the XVT INCLUDE subdirectory) and the .rc files included with the examples, hardcode pathnames that assume the parent directory to be \XVT. This minor inconvenience should be handled by the installation.

The Mac version of XVT contains the BIN, EXAMPLES, INCLUDE, and LIB folders. I loaded the INCLUDE and LIB folders into the Think C folder, and created a new folder to contain the BIN and EXAMPLES folders.

XVT includes nine examples that show off the package's abilities. The best way to sally up to XVT is to first read the technical overview and then follow the examples. These examples cover the use of the clipboard, scrolling, the use of the font style menu, tracking with the mouse, directory manipulation, and dialog boxes. All examples are well thought out and provide a great starting point for applications development.

Taking Issue

I wish XVT handled some characteristics on the PC side differently. In particular, the XVT functions are not in a dynamic link library (DLL), and thereby zapping more of my valuable memory resource and data area than necessary (for more insight into DLL, see "Dynamic Link Libraries Under Microsoft Windows," elsewhere in this issue). Putting the XVT functions into a DLL would offer considerable advantages. The code would be shared not only between instances of an application, but also between applications. Also, any static or global data defined and used exclusively by the library would not eat into an application's precious local data. The Advanced Programming Institute plans to implement the OS/2 library as a DLL, but there are no plans to do the same for the Windows library.

Another problem is that although the XVT functions were created using the medium model, the code segments were not separated. This approach creates a huge code segment (around 60K) that degrades the performance of Windows and, consequently, the performance of the application. If the application is of any size, the performance of Window's memory manager is really improved by breaking the code up into multiple segments. Luckily, this step can be handled by creating a map listing and adding the code segment names to the module definition file. An example is provided in the Installation and Users Guide.

Note that windows in XVT are created with a NULL brush for the background. This means that an application must paint the client rectangle each time it receives an update event. The time this unnecessary call takes to paint the client rectangle each time an update event occurs is quite noticeable.

In Windows, once the application's background brush is set to a non-null brush, the application need not worry about updating the background. When XVT is used on a Mac, application calls EraseRect( ) to update the background. In both of these cases, XVT should take care of updating the background.

The size of the application's main window is predefined as the full screen. This makes the main window look maximized, even though the size boxes are the same as those used for a normal window. If the programmer is not allowed to enter the screen coordinates for the main window, then the size of the main window should default to the default overlapped window size that Windows provides.

One final suggestion that I would make for future versions of XVT is that the system font be used as the default.

Hello World!

The simplest application created with XVT must contain the following:

  • a menu
  • an initial window data structure
  • an application initialization function named appl_init( )
  • an application cleanup function named appl_cleanup( )
  • a main event function named main_event( ) that is called by XVT when one of the 15 defined events occurs

Listings One through Three, illustrate the difference between XVT code (Listing One, page 102), Mac (Listing Two, page 102), and Windows code (Listing Three, page 104) for the classic "Hello World!" example. Figure 1 shows the output from either of these examples. Although the application is very simple, I think it gives the flavor of the way that XVT abstracts the features of the PC and Mac environments. I used Lap-Link (Mac) to transfer files between the two machines. XVThello.c compiled without a hitch in both environments. Once I set up the resource and library files, the programs I tested ran as advertised.

Final Note

To summarize, whether XVT will fill a particular need is clearly up to the application. Some important features, such as the dynamic data exchange in Windows and the more advanced intertask communications in OS/2 Presentation Manager, would be difficult to reproduce in the Mac environment, and are thus not included into XVT. Every day, there is talk about packages soon to be introduced that promise improved portability between GUI implementations. For instance, Microsoft and Glockenspiel have announced a product called CommonView. According to information that I can gather at this time, CommonView is implemented as a DLL and provides C+ + classes for most Windows objects. It claims a high percentage of portability between Windows and OS/2 PM. The developers also hope to have ports for X-Windows, News, and the Mac at some unknown future date. But that's the future, and XVT is available now. I am impressed with both the amount of effort invested in this package and with its capabilities. The folks at the Advanced Programming Institute have shown the impossible dream to be a reality for a significant number of features found in a windowing environment.

_EXAMINING ROOM - THE PORTABILITIY DREAM_ by Margaret Johnson

[LISTING ONE]

<a name="0099_000b">

/**********************************************************************
 XVT "Hello World"
 **********************************************************************/
#include "xvt.h"                     /* standard XVT header */
#include "xvtmenu.h"                 /* standard XVT menu tags */


/*
       Required application setup structure.
*/
APPL_SETUP appl_setup = {
        0,                /* menu bar resource ID (use default) */
        0,                /* about box resource ID (use default) */
        "Hello World!",   /* application's name */
        W_DOC,            /* type of initial window */
        TRUE,             /* size box on initial window? */
        FALSE,            /* vert. scroll bar on initial window? */
        FALSE,            /* horz. scroll bar on initial window? */
        TRUE,             /* close box on initial window? */
        FALSE,            /* want std. font menu? (includes sizes) */
        FALSE             /* want std. style menu? */
};
/*********************************************************************
 *       Main application entry point.
 *********************************************************************/
void main_event(win, ep)
WINDOW win;
EVENT *ep;

{ RCT  rct;

  switch(ep->type)
   {
    case E_UPDATE:
        get_client_rect(win,&rct);
        set_pen(&white_pen);
        draw_rect(&rct);
        draw_text(10,100, "Hello World!", -1);
        break;
    case E_COMMAND:
        if (ep->v.cmd.tag == M_FILE_QUIT)
           terminate();
        break;
    case E_CLOSE:
        terminate();
        break;
    case E_QUIT:
        if (ep->v.query)
              quit_OK();
        else
              terminate();
        break;
    }
}

/***********************************************************************
 *      Application cleanup.  Nothing to do.
 ***********************************************************************/
void appl_cleanup()
{
}
/**********************************************************************
 *       Application initialization.
 **********************************************************************/
BOOLEAN appl_init()
{
 return(TRUE);
}




<a name="0099_000c"><a name="0099_000c">
<a name="0099_000d">
[LISTING TWO]
<a name="0099_000d">

/***********************************************************************
 * Mac "Hello World"
 ***********************************************************************/
#include <QuickDraw.h>
#include <WindowMgr.h>
#include <ControlMgr.h>
#include <EventMgr.h>
#include <DeskMgr.h>
#include <MenuMgr.h>

GrafPtr       w_port;
Rect          drag_rect, grow_bounds;
WindowRecord  w_record;      /* storage for a window's information */
WindowPtr     hello_window; /* a pointer to that storage */

#define  mk_long(x)  (*((long *)&(x)))

main()
{
 init_process();            /* do all the initialization */
 make_window();
 event_loop();
}

/**********************************************************************/
 init_process()
 {
  init_mgrs();
  set_parameters();
 }

/**********************************************************************/
init_mgrs()
{
 InitGraf(&thePort);
 InitFonts();
 FlushEvents(everyEvent,0);
 InitWindows();
 InitCursor();
}
/**********************************************************************/
set_parameters()
{
 drag_rect = thePort->portRect;
 SetRect(&grow_bounds, 64, 64, thePort->portRect.right,
         thePort->portRect.bottom);
}
/**********************************************************************/
make_window()
{
 hello_window = GetNewWindow(128,&w_record,-1L);
}
/**********************************************************************/
event_loop()
{
 EventRecord event;

 while (1)
   {SystemTask();
    GetNextEvent(everyEvent, &event);
    switch(event.what)
       {
        case mouseDown:
            do_mouse_down(&event);
            break;

        case updateEvt:
            do_update(&event);
            break;

        case activateEvt:
            do_activate(&event);
            break;

        default:
            break;
       }
   }
}
/**********************************************************************/
do_mouse_down(eventp)
   EventRecord *eventp;
 {
   WindowPtr  mouse_window;

   switch(FindWindow(mk_long(eventp->where),&mouse_window))
      {
       case inContent:
          if (mouse_window != FrontWindow())
             SelectWindow(mouse_window);
          break;

       case inDrag:
          DragWindow(mouse_window, mk_long(eventp->where),&drag_rect);
          break;

       case inGrow:
          grow_window(mouse_window, mk_long(eventp->where), &drag_rect);
          break;

       case inGoAway:
          if (TrackGoAway(mouse_window,mk_long(eventp->where)))
              finish();
          break;

       default:
          break;
       }
  }
/**********************************************************************/
do_update(event)
  EventRecord  *event;
{
 GrafPtr   save_graf;
 WindowPtr update_window;

 if (FindWindow(mk_long(event->where),&update_window) != inSysWindow)
    {if (update_window == hello_window)
        {GetPort(&save_graf);
         SetPort(update_window);
         BeginUpdate(update_window);
         ClipRect(&update_window->portRect);
         EraseRect(&update_window->portRect);
         DrawGrowIcon(update_window);
         draw_content(update_window);
         EndUpdate(update_window);
         SetPort(save_graf);
        }
     }
}
/**********************************************************************/
do_activate(event)
  EventRecord  *event;
{
 WindowPtr event_window = (WindowPtr)event->message;
 if (event_window == hello_window)
    {DrawGrowIcon(event_window);
     if (event->modifiers & 1)
        SetPort(event_window);
    }
}
/**********************************************************************/
grow_window(window,mouse_point)
  WindowPtr window;
  Point     mouse_point;
{
 long new_bounds;

 inval_bars(window);
 new_bounds = GrowWindow(window, mk_long(mouse_point),&grow_bounds);
 if (0 == new_bounds)
   return;
 SizeWindow(window,LoWord(new_bounds),HiWord(new_bounds),TRUE);
 inval_bars(window);
}
/**********************************************************************/
inval_bars(window)
  WindowPtr  window;
{
 Rect temp_rect, port_rect;

 port_rect = window->portRect;
 SetRect(&temp_rect,port_rect.left,port_rect.bottom-16, port_rect.right,port_rect.bottom);
 InvalRect(&temp_rect);
 SetRect(&temp_rect,port_rect.right-16,port_rect.top, port_rect.right,port_rect.bottom);
 InvalRect(&temp_rect);
}
/**********************************************************************/
draw_content(window)
  WindowPtr window;
{
 MoveTo(100,100);
 DrawString("\pHello World!");
}
/**********************************************************************/
finish()
{
 exit(0);
}




<a name="0099_000e"><a name="0099_000e">
<a name="0099_000f">
[LISTING THREE]
<a name="0099_000f">

#include <windows.h>
#include "hello.h"

BOOL NEAR       Initialize( HANDLE hInst, HANDLE hPrevInst, int nCmdShow );
long FAR PASCAL WndProc   ( HWND hWnd, WORD wMessage, WORD wParam, LONG lParam);
static char  szClass[40];
static char  szTitle[40];

 int PASCAL WinMain( hInst, hPrevInst, lpszCmdLine, nCmdShow )
   HANDLE      hInst;              /* Our instance handle */
   HANDLE      hPrevInst;          /* Previous instance of this application */
   LPSTR       lpszCmdLine;        /* Pointer to any command line params */
   int         nCmdShow;           /* Parameter to use for first ShowWindow */
   {
    MSG        msg;                /* Message structure */

    if( ! Initialize( hInst, hPrevInst, nCmdShow ) )
        return FALSE;

     while( GetMessage( &msg, NULL, 0, 0 ) ) {
         TranslateMessage( &msg );
         DispatchMessage( &msg );
        }

    return msg.wParam;
   }
/************************************************************************
      Initialize the application.
      Returns TRUE if initialization succeeded, FALSE if failed.
 ************************************************************************/
 BOOL NEAR Initialize( hInst, hPrevInst, nCmdShow )
   HANDLE      hInst;        /* Our Instance handle */
   HANDLE      hPrevInst;    /* Previous instance handle, 0 if first */
   int         nCmdShow;     /* Parameter from WinMain for ShowWindow */
   {
    WNDCLASS    WndClass;    /* Class structure for RegisterClass */
    HWND        hWnd;        /* The window handle */
    HMENU       hMenu;       /* Handle to the (system) menu */

    if( ! hPrevInst )
       {
         LoadString( hInst, IDS_CLASS,    szClass,    sizeof(szClass) );
         LoadString( hInst, IDS_TITLE,    szTitle,    sizeof(szTitle) );
         WndClass.style          = CS_HREDRAW | CS_VREDRAW;
         WndClass.lpfnWndProc    = WndProc;
         WndClass.cbClsExtra     = 0;
         WndClass.cbWndExtra     = 0;
         WndClass.hInstance      = hInst;
         WndClass.hIcon          = LoadIcon( NULL, IDI_APPLICATION );
         WndClass.hCursor        = LoadCursor( NULL, IDC_ARROW );
         WndClass.hbrBackground  = GetStockObject(WHITE_BRUSH);
         WndClass.lpszMenuName   = NULL;
         WndClass.lpszClassName  = szClass;

         if( ! RegisterClass( &WndClass ) )
              return FALSE;
       }
    else
       {
         GetInstanceData(hPrevInst, szClass, sizeof(szClass));
         GetInstanceData(hPrevInst, szTitle, sizeof(szTitle));
       }
    hWnd = CreateWindow(
         szClass,          /* Class name */
         szTitle,          /* Window title */

         WS_OVERLAPPEDWINDOW,  /* window style */
         CW_USEDEFAULT,    /* x */
         0,                /* y */
         CW_USEDEFAULT,    /* x width */
         0,                /* y width */
         NULL,             /* Parent hWnd (none for top-level) */
         NULL,             /* Menu handle */
         hInst,            /* Owning instance handle */
         NULL              /* Parameter to pass in WM_CREATE (none) */
     );
    ShowWindow( hWnd, nCmdShow );
    UpdateWindow( hWnd );

    return TRUE;
   }
/***********************************************************************
 Process the messages
***********************************************************************/
 long FAR PASCAL WndProc(hWnd, wMessage, wParam, lParam)
  HWND hWnd;
  WORD wMessage, wParam;
  LONG lParam;

  {PAINTSTRUCT  ps;

   switch (wMessage)
    {case WM_PAINT:
       BeginPaint(hWnd,&ps);
       TextOut(ps.hdc,10,100,"Hello World!",12);
       EndPaint(hWnd,&ps);
       break;
     default:

       return DefWindowProc( hWnd, wMessage, wParam, lParam );
       break;
    }
   return 0L;
  }












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.