Hints for writing simple programs for both OS/2 and DOS

From EDM2
Jump to: navigation, search

By Roger Orr

One of the most obvious characteristics of OS/2 is its similarity to DOS. Many of the commands are the same, and if you are running in a full screen session it can sometimes be a while before you notice whether you are using DOS or OS/2. In fact, to solve this problem my PC is set up to have "DOS" or "OS/2" embedded in the command line prompt!

A major advantage of this similarity is that if you are trying to use both OS/2 and DOS the familiarisation time is reduced. However not many people seem to be aware of the various methods available to do this for their own programs. This article attempts to briefly outline one or two or the ways in which a program can be made to run in a similar manner under both DOS and OS/2.

The most "primitive" way to have similar commands under DOS and OS/2 is to have two separate binary programs and then set the PATH environment variable so it points to the OS/2 program for OS/2 and vice versa. This technique is just like having an 'ls' program under DOS which does almost the same as the UNIX command of the same name.

However, when the two operating systems are related as closely as DOS and OS/2 another technique is very powerful - the concept of a 'bound' program. This is an executable program which runs under BOTH operating systems. It is possible to do this because the header for the OS/2 loader always follows a header for the DOS loader. Usually this DOS header merely loads a 'stub' program which prints the message: "This program cannot be run in DOS mode.", but the program 'BIND' will replace this stub with a DOS program which will load the OS/2 program and map the OS/2 specific calls into calls to code providing equivalent function by, for example, using DOS interrupts.

Example: the standard learning program 'hello.c' to print out 'hello world' can be compiled for OS/2 and then 'bound' to run under DOS as well by:

cl /c hello.c
link hello ;
bind hello c:\lib\doscalls.lib

and now the HELLO.EXE output program will run under OS/2 or DOS

(I am using MicroSoft C5.1, installed for OS/2 programming)

Note that BIND, being a simple minded program, has to be explicitly told where to look for the file DOSCALLS.LIB. The CL command can do this part for you - the three lines above can be replaced with:

cl /Fb hello.c

which is a lot simpler, but hides the mechanics of the operation.

BIND uses another library in addition to DOSCALLS.LIB: API.LIB, which contains the code for DOS to provide some of the OS/2 application programming interface (abbreviated as API, hence its name). The APIs supported by this library are known as Family APIs (FAPI for short) and this means that any program restricting itself to the FAPI calls to OS/2 can be bound in this manner. This includes (almost) all programs which only use the standard C runtime functions.

The FAPI includes functions like DosWrite(), VioWrtTTY(), KbdCharIn(), but not OS/2 specific items like DosCreateThread(). Note also that some of the calls are only partly supported under DOS - for example DosFindNext() is restricted under DOS to only one search handle at a time.

BINDing a program is an extremely attractive idea for two main reasons:

  • Firstly you have ONE source file and ONE executable file. Testing, maintaining and keeping track of the program is easier.
  • Secondly you can develop and test your program under OS/2, which has a better designed API than DOS and a more robust debugging environment, and then be confident of the results under DOS.

What are the problems?

  • Firstly the bound program can only use the features which are common to both OS/2 and DOS, so the OS/2 program is restricted by DOS.
  • Secondly the program will be slower under DOS than if it had originally been compiled solely for DOS because of the extra loading stage.
  • Thirdly some of the FAPI calls are NOT quite identical under OS/2 and DOS, and some have side effects with, for example, the C runtime.

There are however various ways round some of these problems.

The first way is to return to two different executables - but to link them together into one binary, by using the DOS program as the 'stub' for the OS/2 program.


masm prog.asm;
link prog,dosstub.exe;
echo STUB 'dosstub.exe' > prog.def
cl /DOS2 prog.c prog.def

In this example I've assumed you first had a DOS assembler program and since then have written an OS/2 equivalent in C. This is merely a way of combining two programs into one - the DOS and OS/2 programs can be quite different, and so the OS/2 program can have more 'bells and whistles'. It is also a useful technique where the ASM program is very quick and the bound C one isn't!

The second way is to use DosGetMachineMode() to find out whether you are running DOS or OS/2 and act accordingly. For example, a program may wish to 'pop up' an error window. Under OS/2 VioPopUp may be used, but this is NOT a FAPI call. So the program could conditionally call VioPopUp only in OS/2.

BIND allows you to specify, by the option "-n @filename", a file which contains a list of APIs to map to a BadDynLink function and so prevent unresolved references at bind time, which in this example would contain VIOPOPUP and VIOENDPOPUP.

You can also write your own code to provide a suitable equivalent to an OS/2 API and link that with BIND. I have used this for programs which use using NETBIOS, which under DOS is accessed by interrupt 5Ch but under OS/2 is a call.

Example: a short assembler program NETBIOS.ASM is written:

code    segment byte public 'CODE'
        assume  cs:code

        public  NETBIOS
NETBIOS proc    far
        push    es                      ; save registers
        push    bx
        les     bx,dword ptr [bp+4]     ; address of control block
        int     05ch                    ; do the work!
        pop     bx                      ; restore registers
        pop     es

code    ends

I then compile my program (called, for example, netb.c) like this:

masm netbios.asm;
cl netb.c
bind netb netbios.obj c:\lib\doscalls.lib

BIND will locate the NETBIOS procedure in the specified object module and use this code to replace the OS/2 NETBIOS API. In this way you can provide your own DOS functionality for OS/2 DLLs which do not belong to the FAPI but which are achievable under DOS.

In conclusion, there are 20 million machines or so in the world which are running DOS, so it is likely that even the keenest advocate of OS/2 will need to use DOS, or at least to help others to use it. It is worth a little effort to try and make this task as easy as possible and to reduce the learning curve for those upgrading from DOS to OS/2. Methods such as these are a useful part of this process.