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.
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.
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)
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!