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.

No comments: