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

C/C++

Porting D-Flat++ to OS/2


MAY94: Porting D-Flat++ to OS/2

Borland C++ for OS/2 eases the burden

Jon is currently involved with the development of the "Guidelines" Visual C++ Development tool for OS/2 Presentation Manager. He can be contacted on Compu-Serve at 71333,2641.


Borland's entry into the OS/2 world, Borland C++ for OS/2, has opened the door for many DOS developers who have grown used to Borland-specific library extensions. Of course, ANSI specifies a standard library of functions for C and C++, but these functions aren't always adequate for real-world applications, and developers end up supplementing them with extensions. With Borland C++ for OS/2, familiar extensions are available to OS/2 developers.

In this article, I'll describe the toolset Borland has provided for OS/2 and my experiences porting Al Stevens's D-Flat++ (DF++) library to OS/2. Regular readers of DDJ are familiar with this C++ class library which allows you to build a CUA '89-compliant user interface under DOS. (DF++ was first discussed in Al's "C Programming" column in June 1992.) Because the DF++ listings are lengthy, the entire DF++ for OS/2 project is available electronically; see "Availability" on page 3. This article covers Version 1.x of the tools.

Touring the Tools

A complete installation of Borland C++ for OS/2 consumes roughly 27 Mbytes of hard-disk space. Included in the toolset are all the tools you need for C++ development under OS/2. Browsing the directory structure reveals few surprises to veteran Borland C++ programmers. However, when you run the integrated-development environment (IDE) or debugger, you can see that much has been rewritten. These tools take advantage of OS/2 and Presentation Manager (PM) features, while providing interfaces that seem much smoother.

Borland C++ for OS/2 includes a set of header files to access the OS/2 APIs and tools to manipulate and create OS/2-specific files. While IBM has traditionally offered these as a separate toolkit, Borland bundles them with a few modifications. Most of the sample code from the IBM toolkit is also included, although a couple of the larger examples are missing.

A PM version of the Resource Workshop, for instance, combines the functions provided by the separate Font, Icon, and Dialog editors in the IBM toolkit. Borland also provides a 32-bit version of the TASM assembler, which I believe is the only native OS/2 2.x assembler available. (Microsoft removed OS/2 support from MASM at Version 6.1.)

Porting D-Flat++

Porting DF++ to OS/2 was fairly painless. Most of the changes I made are confined to half a dozen classes--Cursor, Keyboard, Mouse, Screen, Speaker, and Timer--that perform platform-dependent access to the hardware. By encapsulating the system interfaces and restricting the dependent code to these modules, Al made the library far easier to port than I had expected. The DOS versions of the affected classes mostly use the int86() and int86x() calls to perform low-level system access, along with inp() and outp() for port I/O. Because OS/2 is a protected-mode operating system, these calls are not permitted. Hardware access requires either a device driver or a 16-bit segment with I/O privilege level. In addition, the BIOS is real-mode code which is not valid in protected mode. In place of these calls, OS/2 provides an API to access system resources. Table 1 is a list of the mappings between the DOS interrupt calls and the OS/2 API.

Listings One and Two (page 110) present the Cursor class. The class definition was modified to include a VIOCURSORINFO structure instead of the registers structure in the original DF++. This is used to hold details of the cursor type when communicating with the VioGetCurType() and VioSetCurType() APIs. The other pair of APIs, VioGetCurPos() and VioSetCurPos(), both use 16-bit x and y values.

With the Screen class (see "Availability," page 3), I chose to simplify the interface to the class by removing the Page(), isEGA(), isVGA(), isMono(), and isText() member functions. These were only used within the screen module, so they weren't really part of the public interface. I also removed the address, page, and mode data members that these functions used. I replaced them with a VIOMODEINFO structure that returns the screen dimensions using a VioGetMode() call. The Keyboard class, also available electronically, maps OS/2's KbdXxx functions to the DOS int 16h calls used in the original DF++; see Table 1. In the constructor for the Keyboard instance, it's necessary to open the keyboard as a device, then grab the focus. The system state of the keyboard is then set to binary (RAW) mode, and shift state reading is turned on.

In the Speaker class (Listings Three and Four, page 110), the Beep method no longer needs to do its own timing or write directly to the speaker hardware; the function maps well onto DosBeep(); see Table 1.

The Timer class (Listing Five, page 110, and Listing Six, page 111) was rewritten to use OS/2 system timers, as opposed to the original approach, which involves the hooking of an interrupt to periodically update a static-timer array. Borland already has a timer.hpp, so I changed the name of the files to dtimer.hpp and dtimer.cpp, respectively. (I've found that relying on path position to distinguish between identically named files can lead to confusion.) High-resolution timing is not required, so the OS/2 DosAsyncTimer() and DosStopTimer pair is adequate. When the timer expires, an Event semaphore is posted, which may be queried at any time to discover if the timer is running. Note that the APIs used in this module are OS/2 2.x specific and thus will not work under 16-bit versions of OS/2. This wasn't an issue for me (Borland C++ for OS/2 will only generate 32-bit code), but alternative schemes could be devised.

Mouse handling was the trickiest to understand and implement correctly. It took me some time to appreciate that modal windows are constructed within the Mouse::DispatchEvent call. With D-Flat++, a modal window, such as a message box, suspends processing of the mouse event that invoked it and starts its own version of the event-processing loop until it terminates. Processing of the original event then resumes. Thus, the dispatcher must be reentrant. Failing to appreciate this, I coded the mouse-event dispatcher as a state machine, which caused several hours of head scratching. The moral is to beware of assumptions! I modified my mouse handler, setting the next state before dispatching the event, but a better approach could probably be found.

Drawing a state diagram showed that only one timer was required, rather than the two in the original implementation. Therefore, I removed the Moved(), LeftButton(), and ButtonReleased() member functions. These were only used internally to the module and were not necessary for the ported module. Data members were added to hold the timing constants that determine whether two adjacent clicks are actually a double click and the typematic behavior. A dialog could be written to manipulate these to accommodate user preferences.

Program Notes

When writing the mouse interface, I ran across an inconsistency between Presentation Manager and CUA guidelines. According to CUA '91 (the version PM follows), double-clicking is defined thus: "press and release a button on a pointing device twice while a pointer is within the limits that the user has specified for the operating environment." PM itself doesn't wait for the second release; it acts on the preceding click. D-Flat++ follows CUA '89 (the version of the standard used in Windows), so that's how I implemented it, too. In practice, the difference is not really noticeable.

Under OS/2 text mode, performance can suffer unless text is written to the screen in longer chunks. As it stands, DF++ usually writes a character at a time, so this could be improved. On a development machine, performance is acceptable with the test program. But on a slower machine, the tracking rectangle tended to wallow about, trying to follow the mouse. With the text editor (TED) in the most recent DF++, clearing the screen at start-up is slow and could stand some redesign.

An interesting side effect of porting DOS code to OS/2 is the discovery of latent defects. By running the application under the TD-GX debugger, you can see that each time a dereference of a null pointer (for instance) is attempted, TD-GX handles the exception and puts you back in control, opening windows to show the source line where the exception occurred, the disassembled instruction, the call stack, and other information. This caught a couple of problems which had made it into the original code as posted (and which appear to run fine under DOS). This may make the case for OS/2 as a first platform for development, with a later port to DOS when the code runs smoothly.

Plus and Minus

Version 1.x of the Borland OS/2 compiler revealed a number of shortcomings (although Version 2.0, which addresses many of these problems, may be available as you read this). In particular:

  • TLINK's lack of support for Thread Fixup records (which causes problems when linking with code generated by IBM's C Set ++ compiler).
  • A limit of 16 Mbytes for static arrays.
  • A limit of 68 threads allowed per process.
  • A limit of 40 file handles (in the run-time code).
  • Improper thunking handling (16 <-> 32-bit conversion).
The thread-fixups problem was corrected in a maintenance release, but I did not have time to retest the other areas.

Overall, the compiler was speedy when compared with IBM tools. However, this speed comes at a price--optimization is not as good as with IBM C Set ++ or Watcom C++ 9.5, either of which would be preferable for a final compile of production code on new projects (see the accompanying text box entitled, "IBM's C Set ++").

The Borland debugger does exploit OS/2's capabilities. It debugs both character-mode and PM programs, though a few major flaws make some PM apps impossible to debug successfully. One flaw is that it starts debuggee programs as children and doesn't allow you to bring them to the foreground from within the tool. The child can easily be completely hidden behind the debugger

windows, requiring use of the system task list to bring it to the foreground.

Borland's IDE from DOS was carried on to OS/2 and rewritten to take advantage of the Presentation Manager and CUA '91 controls. Settings for particular projects are adjusted using "Notebooks," a form of multipage dialog. Help is comprehensive, and context-sensitive help (using Ctrl+F1) for the OS/2 APIs was added with the maintenance release. To achieve this, Borland has included .HLPversions of the .INFfiles, which increases the amount of disk space required considerably. This is because OS/2 treats INF and HLP files differently, although internally, the difference is one byte. Color highlighting of syntax works well in the editor, a big plus being the easy detection of missing ends to comments which can result in large tracts of code being effectively excluded from the compile.

I tend to work on larger multidirectory projects and have set up a build system with a third-party make tool to coordinate all sources. The IDE is based around a single project/source tree, with no provision for version control, which I found a bit restricting. This, coupled with my enthusiasm for a different editor, led me to do most of my work by integrating the command-line version of the Borland tools into my existing system.

Although the main project was to get D-Flat++ working under OS/2, I thought I'd also try out the Presentation Manager with a small piece of code. Using the Hexadecimal calculator in Charles Petzold's Programming the OS/2 Presentation Manager (Microsoft Press, 1989), I wrote a 32-bit C++ version of the original 16-bit C program, using the Borland-supplied samples as a template. The calculator program came together very easily in one evening. No class libraries were needed, which was fortunate, as Borland's Object Windows Library (OWL) was not a part of the first release for the OS/2 platform. For this example, I used the Resource Workshop for creating the dialog resources.

Conclusion

As it stands, Borland's OS/2 C++ tools are good. My main concern with using them on a large project would be Borland's support record. Where a large body of legacy code needs to be ported from a DOS environment, the availability of the Borland extensions makes the task easier.

Borland C++ for OS/2 is easy to set up and use, fast, and a good starting point for OS/2 programming, especially for programmers coming from DOS and Windows. Its Resource Workshop and Assembler components in particular guarantee it a place in my toolbox.

Table 1: Mappings between DOS interrupt calls and the OS/2 API.

DOS Interrupt             OS/2 Function
------------------------------------------
Int 10, Func 01h          <I>VioSetCurType()</I>
Int 10, Func 02h     <I>     VioSetCurPos()</I>
Int 10, Func 03h     <I>     VioGetCurPos()</I>
                          <I>VioGetCurSize()</I>
Int 10, Func 06h     <I>     VioScrollUp()</I>
Int 10, Func 07h     <I>     VioScrollDn()</I>
Int 10, Func 12h     <I>     VioGetMode()</I>
Int 10, Func 15h     <I>     VioGetMode()</I>
Int 10, Func 1Ah     <I>     VioGetMode()</I>
<I>peek()                    VioReadCellStr()</I>
<I>poke(), movedata()        VioWrtCellStr()</I>
Int 16, Func 00h     <I>     KbdCharIn()</I>
Int 16, Func 01h     <I>     KbdPeek()</I>
Int 16, Func 02h     <I>     KbdGetStatus()</I>

IBM compiler technology is just as mature and possesses all the functionality of Borland C++, but it takes a different philosophical approach in its fundamental design. While the Borland compiler provides a powerful set of vendor-specific extensions, the C Set ++ (C and C++) compiler is built with an emphasis on correct, optimized code and provides a high degree of ANSI, ARM, and DWP compliance. For instance, it is able to globally optimize programs across source files, and provides templates and exception handling.

However, such capabilities come at a price. The compiler expects to run on a machine with a typical configuration of 80486 with 16 Mbytes of RAM and 100 Mbytes free on a fast hard drive (65 Mbytes of files and 30 Mbytes swap space). C Set ++ will run on half the memory and disk space but with a penalty in performance and available features. By contrast, Borland's compiler will run twice as fast in 8 Mbytes of RAM.

IBM provides tools and class libraries--such as a Browser and Profiler--that Borland does not. IBM's class libraries use exception handling and templates throughout, and range from basic collection classes to an extensive high-level encapsulation of the Presentation Manager API.

Also significant is the issue of support. Borland accumulates six to twelve months worth of fixes, then does a major-upgrade release. IBM releases fixes frequently, with a program to install them. These fixes are available electronically, or mailed free if you report one of the bugs that it fixes. Both Borland and IBM support teams on CompuServe, where much of the support effort is concentrated. Borland uses dedicated support engineers, while IBM support includes product developers and managers.

Borland's audience will mostly be developers moving to OS/2 from the DOS/Windows world. They are familiar with the interface and tools. At first glance, IBM seems pitched towards larger development projects and the professional-compiler market. However, C Set ++ pricing puts it firmly in the same league as Borland, and with decreasing hardware prices, Borland will need to run fast to keep up.

--J.W.

IBM's C Set ++

Borland C++ for OS/2
Borland
1800 Green Hills Road
Scotts Valley, CA 95066

[LISTING ONE]


// ------------- cursor.h  -- modified for OS/2 operation - jw21sep93
#ifndef CURSOR_H
#define CURSOR_H

#define INCL_BASE
#define INCL_NOPMAPI
#include <os2.h>

const int MAXSAVES = 50;  // depth of cursor save/restore stack
class Cursor
    {
    VIOCURSORINFO ci;
    // --- cursor save/restore stack
    int  cursorpos[MAXSAVES];
    int  cursorshape[MAXSAVES];
    int  cs;                     // count of cursor saves in effect
    void Cursor::GetCursor();
public:
    Cursor();
    ~Cursor();
    void SetPosition(int x, int y);
    void GetPosition(int &x, int &y);
    void SetType(unsigned t);
    void NormalCursor() { SetType(0x0607); }
    void BoxCursor()    { SetType(0x0107); }
    void Hide();
    void Show();
    void Save();
    void Restore();
    void SwapStack();
    };
inline void swap(int& a, int& b)
    {
    int x = a;
    a = b;
    b = x;
    }
#endif


[LISTING TWO]



// ------------ cursor.cpp -- modified for OS/2 operation - jw13nov93

#include <dos.h>
#include "cursor.h"
#include "desktop.h"

Cursor::Cursor()
    {
    VioGetCurType(&ci, 0);
    cs = 0;
    Save();
    }
Cursor::~Cursor()
    {
    Restore();
    }
// ------ get cursor shape and position
void Cursor::GetCursor(){};
// -------- get the current cursor position
void Cursor::GetPosition(int &x, int &y)
    {
    USHORT  sx, sy;
    VioGetCurPos(&sy, &sx, 0);
    x = sx;
    y = sy;
    }
// ------ position the cursor
void Cursor::SetPosition(int x, int y)
    {
    VioSetCurPos((USHORT)y, (USHORT)x, 0);
    }
// ------ save the current cursor configuration
void Cursor::Save()
    {
    USHORT x, y;
    if (cs < MAXSAVES)
        {
        VioGetCurPos(&y, &x, 0);
        cursorpos[cs]   = (x<<8) | y;
        VioGetCurType(&ci, 0);
        cursorshape[cs] = (ci.yStart << 8) | ci.cEnd;
        cs++;
        }
    }
// ---- restore the saved cursor configuration
void Cursor::Restore()
    {
    USHORT row = (USHORT)(cursorpos[cs] & 0xff);
    USHORT col = (USHORT)((cursorpos[cs] >> 8) & 0xff);
    if (cs)
        {
        --cs;
        VioSetCurPos(row, col, 0);
        SetType(cursorshape[cs]);
        }
    }
/* ---- set the cursor type ---- */
void Cursor::SetType(unsigned t)
    {
    ci.yStart = (USHORT)((t >> 8) & 0xff);
    ci.cEnd   = (USHORT)(t & 0xff);
    VioSetCurType(&ci, 0);
    }
/* ----- swap the cursor stack ------- */
void Cursor::SwapStack()
    {
    if (cs > 1)
        {
        swap(cursorpos[cs-2], cursorpos[cs-1]);
        swap(cursorshape[cs-2], cursorshape[cs-1]);
        }
    }
/* ------ hide the cursor ------ */
void Cursor::Hide()
    {
    USHORT t;
    t = ci.attr;
    ci.attr = 0xffff;
    VioSetCurType(&ci, 0);
    ci.attr = t;
    }
/* ------ show the cursor ------ */
void Cursor::Show()
    {
    VioSetCurType(&ci, 0);
    }


[LISTING THREE]



// --------- speaker.h
#ifndef SPEAKER_H
#define SPEAKER_H

#define INCL_BASE
#include <os2.h>
class Speaker
    {
public:
    void Beep();
    };
#endif


[LISTING FOUR]



// -------- speaker.cpp -- modified for OS/2 operation - jw21sep93

#include "speaker.h"
#include "dflatdef.h"
void Speaker::Beep()
    {
    DosBeep(1000, 250);
    }


[LISTING FIVE]



// ----------- dtimer.h -- modified for OS/2 operation - jw21sep93
// uses OS/2 Timer services rather than doing own timer management.
// renamed to avoid collision with Borlands Timer.h

#ifndef TIMER_H
#define TIMER_H

#define INCL_BASE
#define INCL_DOSDATETIME
#define INCL_NOPMAPI
#include <os2.h>

#include "dflatdef.h"
class Timer
    {
    HEV     sem;
    HTIMER  timer;
    APIRET  rc;
    Bool    disabled;
public:
    Timer();
    ~Timer();
    Bool    TimedOut();
    void    SetTimer(int ticks);
    void    DisableTimer();
    Bool    TimerRunning();
    void    Countdown()          { ; }
    Bool    TimerDisabled()      { return disabled; }
    };
#endif


[LISTING SIX]



// -------- dtimer.cpp -- OS/2 Version created to use the OS/2 Timer Services
// jw21sep93  -- renamed from 'timer.cpp' to match header

#include <stdio.h>
#include "dtimer.h"

Timer::Timer()
    {
    // create semaphore
    ULONG SemAttr = DC_SEM_SHARED;      // needs to be shared
    disabled = True;                    // start in disabled state
    timer = 0;
    rc = DosCreateEventSem(NULL, &sem, SemAttr, 1);
    if(rc != NO_ERROR)
        {
        printf("DosCreateEventSem failed: rc = %d\n",rc);
        }
    }
Timer::~Timer()
    {
    rc = DosStopTimer(timer);
    rc = DosCloseEventSem(sem);
    }
void Timer::SetTimer(int ticks)
    {
    ULONG ct;
    disabled = False;
    if (timer)
        {
        rc = DosStopTimer(timer);
        }
    rc = DosResetEventSem(sem, &ct);
    if(rc != NO_ERROR  && rc != ERROR_ALREADY_RESET)
        {
        printf("DosResetEventSem failed: rc = %d\n",rc);
        }
    rc = DosAsyncTimer(ticks*18, (HSEM)sem, &timer);
    if(rc != NO_ERROR)
        {
        printf("DosAsyncTimer failed: rc = %d\n",rc);
        }
    }
void Timer::DisableTimer()
    {
    disabled = True;
    }
Bool Timer::TimedOut()
    {
    ULONG ct = 0L;
    if (disabled == False)
        {
        rc = DosQueryEventSem(sem, &ct);
        if(rc != NO_ERROR)
            {
            printf("DosQueryEventSem failed: rc = %d\n",rc);
            }
        }
    return (Bool) (ct != 0L);
    }
Bool Timer::TimerRunning()
    {
    ULONG ct;
    if (disabled == False)
        {
        rc = DosQueryEventSem(sem, &ct);
        if(rc != NO_ERROR)
            {
            printf("DosQueryEventSem failed: rc = %d\n",rc);
            }
        return (Bool) (ct == 0L);
        }
    return(False);
    }


Copyright © 1994, 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.