2020-04-08

How to cross-compile to various EXE files using Digital Mars C Compiler on Linux

This blog post explains how to use Digital Mars C Compiler 8.57 on Linux with Wine to cross-compile to EXE files of 16-bit DOS, 32-bit DOS and 32-bit Windows (Windows 95 -- Windows 10). The actual program compiled is quite dumb (it doesn't matter), the focus is on installation and command-line flags.

Please note that Digital Mars C Compiler hasn't been ported to Linux, so we need to run it in emulation: we will be running the Win32 version in Wine. (Another option would be running the DOS version in DOSBox.) Digital Mars C Compiler is now open source, you may want to contribute porting it to Linux here.

Digital Mars C Compiler is very small: version 8.57 is less than 13 MiB, including C and C++ compiler, linker, DOS extender, #include files and libc for 16-bit DOS, 32-bit DOS and 32-bit Windows. If compressed with 7z (LZMA2), it's about 2.28 MiB. It's also impressive that all of it (except for the DOS extender) was written by a single person. See also discussion 1 and duscussion 2 on Hacker News, with replies from the author of the compiler.

All commands below are to be run in a Linux terminal window without the leading $.

Check that Wine is installed:

$ wine --version
wine-4.0.3 (Debian 4.0.3-1)

If you get an error message instead of a version number, then install Wine first using your package manager.

Download and extract the compiler:

$ mkdir digitalmarsc
$ R=http://ftp.digitalmars.com/Digital_Mars_C++/Patch/
$ wget -O digitalmarsc/dm857c.zip "$R"/dm857c.zip
$ (cd digitalmarsc && unzip dm857c.zip && rm -f dm857c.zip)
$ wget -O digitalmarsc/dm850dos.zip "$R"/dm850dos.zip
$ (cd digitalmarsc && unzip -n dm850dos.zip && rm -f dm850dos.zip)
$ wget -O digitalmarsc/dm831x.zip "$R"/dm831x.zip
$ (cd digitalmarsc && unzip -n dm831x.zip && rm -f dm831x.zip)
$ KR=https://github.com/Olde-Skuul/KitchenSink/raw/master/sdks/dos/x32/
$ wget -O digitalmarsc/dm/lib/cx.obj  "$KR"/cx.obj
$ wget -O digitalmarsc/dm/lib/x32.lib "$KR"/x32.lib
$ wget -O digitalmarsc/dm/lib/zlx.lod "$KR"/zlx.lod

Please note that cd857.zip contains additional files and it overlaps with dm850dos.zip (e.g. dm/lib/sds.lib).

Create the test program source code:

$ cat >myprog.c <<'END'
#include <stdio.h>  /* Code below works without it. */
const int answer = (int)0x44434241;  /* dd 'ABCD' */
int mul(int a, int b) {
  return a * b + answer;
}
int main(int argc, char **argv) {
  (void)argv;
  return mul(argc, argc);
}
END

There is no need to set up environment variables, because Digital Mars C Compiler can its library files nearby its own directory. But we make the shorthand wine dmc work for convenience:

$ export WINEPATH="$(winepath -w digitalmarsc/dm/bin)"

Do this to prevent Wine from displaying GUI windows:

$ unset DISPLAY

Compile to all targets:

$ wine dmc    -ml         -v0 -odcprog.dosl.exe  myprog.c
$ wine dmc    -ms         -v0 -odcprog.doss.exe  myprog.c
$ wine dmc    -mx x32.lib -v0 -odcprog.dos32.exe myprog.c
$ wine dmc    -mn         -v0 -odcprog.win32.exe myprog.c
$ wine dmc -c -ml         -v0 -odcprog.dosl.obj  myprog.c
$ wine dmc -c -ms         -v0 -odcprog.doss.obj  myprog.c
$ wine dmc -c -mx         -v0 -odcprog.dos32.obj myprog.c
$ wine dmc -c -mn         -v0 -odcprog.win32.obj myprog.c
$ wine dmc -c -mf         -v0 -odcprog.os232.obj myprog.c

More information about running Digital Mars C Compiler: https://digitalmars.com/ctg/sc.html . Please note that the dmc and sc commands were equivalent, but only dmc works in recent versions.

Check the file type of the created executable (*.exe) files:

$ file dcprog.*.exe
dcprog.dos32.exe: MS-DOS executable, MZ for MS-DOS
dcprog.dosl.exe:  MS-DOS executable
dcprog.doss.exe:  MS-DOS executable
dcprog.win32.exe: PE32 executable (console) Intel 80386, for MS Windows

Check the file type of the created object (relocatable, *.obj) files:

$ file dcprog.*.obj
dcprog.dos32.obj: 8086 relocatable (Microsoft), "myprog.c"
dcprog.dosl.obj:  8086 relocatable (Microsoft), "myprog.c"
dcprog.doss.obj:  8086 relocatable (Microsoft), "myprog.c"
dcprog.os232.obj: 8086 relocatable (Microsoft), "myprog.c"
dcprog.win32.obj: 8086 relocatable (Microsoft), "myprog.c"

More information about the OMF OBJ file format (*.obj above): https://pierrelib.pagesperso-orange.fr/exec_formats/OMF_v1.1.pdf

The *.obj files contain an memory model comment with code 0x9d which depends on the target and operating system: dosl has 0lO, doss has 0sO, dos32 has 3fOpd, dosx has 7xO, os232 has fnO, win32 has 7nO.

Alternatives for cross-compiling C code to some of these EXE targets on Linux:

  • OpenWatcom ((16-bit and 32-bit) * (DOS, Windows and OS/2) targets, see blog post for details)
  • Digital Mars C compiler (on Linux needs Wine, 16-bit DOS, 32-bit DOS, 32-bit Windows targets, plus 32-bit OS/2 object target (no linking), see above for details)
  • mingw-w64 (win32 and win64 targets)
  • gcc-ia16 (doss and dosl targets, i.e. 16-bit DOS EXE with various memory models)
  • djgpp-linux32 (dosx target, needs cwsdpmi.exe or pmodstub.exe (PMOED/DJ) as separate downloads)
  • You may be able to run C compilers released for Windows (e.g. the Digital Mars C compiler) using Wine.

2020-04-07

How to cross-compile to various EXE files using OpenWatcom C compiler on Linux

This blog post explains how to use OpenWatcom 2.0 C compiler on Linux (i386 or amd64, any distribution) to cross-compile to EXE files of 16-bit DOS, 32-bit DOS, 16-bit Windows (Windows 3.1), 32-bit Windows (Windows 95 -- Windows 10), 16-bit OS/2 (1.x) and 32-bit OS/2 (2.x). The actual program compiled is quite dumb (it doesn't matter), the focus is on installation and command-line flags.

All commands below are to be run in a Linux terminal window without the leading $.

Download and extract the compiler:

$ mkdir open-watcom-2 open-watcom-2/tmp
$ R=https://github.com/open-watcom/open-watcom-v2/releases
$ wget -O open-watcom-2/tmp/open-watcom-2.zip \
    "$R"/download/Current-build/open-watcom-2_0-c-linux-x86
$ (cd open-watcom-2/tmp && unzip open-watcom-2.zip)
$ (cd open-watcom-2/tmp && mv binl h lib286 lib386 ../)
$ (cd open-watcom-2/tmp && cp binw/dos32a.exe ../binl/)
$ (cd open-watcom-2/tmp && cp binw/dos4gw.exe ../binl/)
$ (cd open-watcom-2/binl && chmod +x owcc wcc wcc386 wlink)
$ rm -rf open-watcom-2/tmp

Create the test program source code:

$ cat >myprog.c <<'END'
#include <stdio.h>  /* Code below works without it. */
const int answer = (int)0x44434241;  /* dd 'ABCD' */
int mul(int a, int b) {
  return a * b + answer;
}
int main(int argc, char **argv) {
  (void)argv;
  return mul(argc, argc);
}
END

Set up compilation environment:

$ export WATCOM="$PWD/open-watcom-2"
$ export PATH="$WATCOM/binl:$PATH" INCLUDE="$WATOM/h"

Compile to all targets:

$ owcc    -bdos -mcmodel=l -o owprog.dosl.exe  myprog.c
$ owcc    -bdos -mcmodel=s -o owprog.doss.exe  myprog.c
$ owcc    -bdos32a         -o owprog.dos32.exe myprog.c
$ owcc    -bdos4g          -o owprog.dosx.exe  myprog.c
$ owcc    -bos2            -o owprog.os216.exe myprog.c
$ owcc    -bos2v2          -o owprog.os232.exe myprog.c
$ owcc    -bwindows        -o owprog.win16.exe myprog.c
$ owcc    -bnt             -o owprog.win32.exe myprog.c
$ owcc -c -bdos -mcmodel=l -o owprog.dosl.obj  myprog.c
$ owcc -c -bdos -mcmodel=s -o owprog.doss.obj  myprog.c
$ owcc -c -bdos32a         -o owprog.dos32.obj myprog.c
$ owcc -c -bdos4g          -o owprog.dosx.obj  myprog.c
$ owcc -c -bos2            -o owprog.os216.obj myprog.c
$ owcc -c -bos2v2          -o owprog.os232.obj myprog.c
$ owcc -c -bwindows        -o owprog.win16.obj myprog.c
$ owcc -c -bnt             -o owprog.win32.obj myprog.c

More information about OpenWatcom and running it on Linux: https://wiki.archlinux.org/index.php/Open_Watcom

Check the file type of the created executable (*.exe) files:

$ file owprog.*.exe
owprog.dos32.exe: MS-DOS executable, LE executable for MS-DOS, DOS/32A DOS extender (embedded)
owprog.dosl.exe:  MS-DOS executable, MZ for MS-DOS
owprog.doss.exe:  MS-DOS executable, MZ for MS-DOS
owprog.dosx.exe:  MS-DOS executable, LE executable
owprog.os216.exe: MS-DOS executable, NE for OS/2 1.x (EXE)
owprog.os232.exe: MS-DOS executable, LX for OS/2 (console) i80386
owprog.win16.exe: MS-DOS executable, NE for MS Windows 3.x (EXE)
owprog.win32.exe: PE32 executable (console) Intel 80386, for MS Windows

FYI the created owprog.dosx.exe file also needs the dos4gw.exe DOS Extender to run. You can copy the file from the binl directory.

Check the file type of the created object (relocatable, *.obj) files:

$ file owprog.*.obj
owprog.dos32.obj: 8086 relocatable (Microsoft), ".../myprog.c"
owprog.dosl.obj:  8086 relocatable (Microsoft), ".../myprog.c"
owprog.doss.obj:  8086 relocatable (Microsoft), ".../myprog.c"
owprog.dosx.obj:  8086 relocatable (Microsoft), ".../myprog.c"
owprog.os216.obj: 8086 relocatable (Microsoft), ".../myprog.c"
owprog.os232.obj: 8086 relocatable (Microsoft), ".../myprog.c"
owprog.win16.obj: 8086 relocatable (Microsoft), ".../myprog.c"
owprog.win32.obj: 8086 relocatable (Microsoft), ".../myprog.c"

More information about the OMF OBJ file format (*.obj above): https://pierrelib.pagesperso-orange.fr/exec_formats/OMF_v1.1.pdf

The *.obj files contain an Intel-specific comment with code 0x9b which depends on the target and operating system: dosl has 0lOed, doss has 0sOed, dos32 has 3fOpd, dosx has 3fOpd (the .obj file is the same for dos32, dosx, os232, win32), os216 has 0sOed (the .obj file is the same for os216 and doss), os232 has 3fOpd (the .obj file is the same for dos32, dosx, os232, win32), win16 has 0sOed (the .obj file differs by only 2 bytes in doss and win16), win32 has 3fOpd (the .obj file is the same for dos32, dosx, os232, win32).

Alternatives for cross-compiling C code to some of these EXE targets on Linux:

  • OpenWatcom ((16-bit and 32-bit) * (DOS, Windows and OS/2) targets, see above for details)
  • Digital Mars C compiler (16-bit DOS, 32-bit DOS, 32-bit Windows targets, plus 32-bit OS/2 object target (no linking), see blog post for details)
  • mingw-w64 (win32 and win64 targets)
  • gcc-ia16 (doss and dosl targets, i.e. 16-bit DOS EXE with various memory models)
  • djgpp-linux32 (dosx target, needs cwsdpmi.exe or pmodstub.exe (PMOED/DJ) as separate downloads)
  • You may be able to run C compilers released for Windows (e.g. the Digital Mars C compiler) using Wine.