Building executables for old versions of windows without redistribute packages (or: Ode to msvcrt.lib & msvcrt.dll duo)

TL;DR Even old windows versions (2000+) have the basic runtime environment available in msvcrt.dll, while recent Visual Studio links against a newer msvcrXX.dll (from 50 to 130), which require additional installation from the user. To build an executable to work “out-of-the-box” – set Visual Studio to use msvcrt.lib, and run some commands described below.

History Lesson

Since the dawn of time, writing in C would imply using a run-time environment, providing functions as simple as strlen(), among other things. With time, new API was added, Microsoft produced newer versions of the run-time environment.  Visual C++ (AKA Visaul Studio) versions 4.2 to 6.0 links against msvcrt.dll, which is shipped with Windows 2000 and onward. Other versions link against different versions of the run-time environment named msvcrXX.dll, which can be as old as msvcr20.dll or as new as msvcr130.dll on VS2013 (which is the latest version of VS as of the date these lines being written), and onward (140, 150…) as new versions of VS will be published and this post becomes an integral part of history ). Here’s the catch: Windows versions are not shipped with these newer run-time environments.

Symptoms

An executable built with Visual studio, run on a clean Windows installation, will produce a “missing mscvrXX.dll” error. To rectify this, you are expected to install the “redistribution package” corresponding to the Visual Studio you used – which contains the msvcrXX.dll it used. This means the executable cannot work out-of-the-box.

Solution – step #1 : Obtain original run-time environment

To build a truly portable executable (Hint of irony: the Windows executable file format is called “PE” for “portable executable”), you must obtain a copy of the famous msvcrt.dll (and msvcrt.lib, and other related stuff – like header files). One way is to find a copy of VS6 (Yes, Visual Studio C++ v6.0), and copy the directoy “D:\VC98” (if D: is the letter for the location of VS6) to a local directory. I’ll demonstrate with “C:\VC98” as the local directory.

SOLUTION – STEP #2: Set PROject parameters to use it

Below is a list of settings which were tested for such a build. You may get away with setting only some. The settings were taken from Visual Studio 2013.

  • C/C++ -> Optimization -> Optimization set to Disabled (/Od)
  • C/C++ -> Optimization -> Enable intrisic functions set to No
  • C/C++ -> Code generation -> Enable C++ Exceptions set to No
  • C/C++ -> Code generation -> Runtime Library set to Multi-threaded (/MT)
  • C/C++ -> Code generation -> Security Check set to Disable Security Checks (/GS-)
  • C/C++ -> Code generation -> Enable Parallel Code Generation set to No (/Qpar-)
  • C/C++ -> Code generation -> Enable Enhanced Instruction Set set to No Enhanced Instructions (/arch:IA32)
  • C/C++ -> Code generation -> Floating Point Model set to Strict (/fp:strict)
  • C/C++ -> Code generation -> Enable Floating Point Exceptions set to No (/fp:except-)
  • Linker -> General -> Additional Library Directories set to C:\VC98\LIB
  • Linker -> Input -> Additional Dependencies set to msvcrt.lib;%(AdditionalDependencies)
    • You can add additional .lib files here, as long as it’s supported in the older version you’re about to use (look inside C:\VC98\LIB).
  • Linker -> System -> Minimum Required Version set to 5.01

Don’t forget: If you really want to build a portable executable – build x86 and not x64 (it’s also the default).

SOLUTION – STEP #3: Amend project to use OLD(er) functions

If your project build now fails miserably – you’re doing it right! Many functions don’t exist in this ancient run-time version, for example: any *_s() string operations (strcpy() is in, strcpy_s() isn’t). There’s no good way to get around this: you either convert your code to use the older functions available, or incorporate an implementation for strcpy_s() and the rest of the missing functions in your project. run-time source codes are available, but it may be more code that your entire project!

Another workaround, ugly as hell, is to create an adapter for function prototypes. In this example, a newer ftol2() is replaced by ftol() by adding this text in a separate source file, instead of modifying the original code. This is also useful if your code uses a binary, built against a newer run-time version and now expects unavailable symbols.

long _ftol(double in);

long _ftol2(double in)
{
return _ftol(in);
}

SOLUTION – STEP #4: Modify executable version demands

The executable should now be built so that all the required code is present on any Windows from 2000 and on, but it’ll still fail – claiming it’s “not a valid win32 application” (on some versions). So, here’s a bit of useful information: PE format contains a requirement as to the version of Windows running it. How to read it?

C:\VC98\BIN\dumpbin.exe /headers <path-to-executable>

FILE HEADER VALUES
     14C machine (i386)
       6 number of sections
306F7A22 time date stamp Sun Oct 01 22:35:30 1995
       0 file pointer to symbol table
     1D1 number of symbols
      E0 size of optional header
     302 characteristics
            Executable
            32 bit word machine
            Debug information stripped

OPTIONAL HEADER VALUES
     10B magic #
    2.60 linker version
    1E00 size of code
    1E00 size of initialized data
       0 size of uninitialized data
    1144 address of entry point
    1000 base of code
    3000 base of data
         ----- new -----
 2BB0000 image base
    1000 section alignment
     200 file alignment
       3 subsystem (Windows CUI)
    4.00 operating system version
    4.00 image version
    3.50 subsystem version
    8000 size of image
     400 size of headers
    62C8 checksum
  100000 size of stack reserve
    1000 size of stack commit
  100000 size of heap reserve
    1000 size of heap commit
       0 [       0] address [size] of Export Directory
    5000 [      3C] address [size] of Import Directory
    6000 [     394] address [size] of Resource Directory
       0 [       0] address [size] of Exception Directory
...

 

See the underlined lines? these are from an old executable, so it’s OK, but VS2013 will produce executable files demanding version 6.00 or higher. Here’s how the numbers correspond to windows versions.

More importantly: How to change it?

C:\VC98\BIN\editbin.exe <path-to-executable> /SUBSYSTEM:WINDOWS,5.01 /OSVERSION:5.1

Note that while this may result with some errors – the demand is still changed!

SOLUTION – STEP #5: BAsk in your glory (and brag)

Your executable is now truely portable. Try it!

Building executables for old versions of windows without redistribute packages (or: Ode to msvcrt.lib & msvcrt.dll duo)

Leave a Reply

Your email address will not be published. Required fields are marked *