C/C++ EXEs and DLLs created by Visual Studio 2008 don't run on Windows 4.0 (ie, NT4 and Win9x)

Louis Solomon / SteelBytes
Written 22/Apr/08
Revised 23/Apr/08 typo's and comment about link.exe/editbin.exe, and the 'quick notes'
Added Link 25/Aug/08 My command line tool that patches compiled code ExeVersion
Added Link 27/Aug/08 other related work LegacyExtender

Here is the result of my exploring this problem ... YMMV :-)

Part 1 (NT4 and Win9x): the required OS version flags in the .exe header are set to 5.0. Can you use Link.exe's or EditBin.exe's /SUBSYSTEM switch to fix this? No, as they don't let you set a value less then 5.0 on both the OS and SubSystem fields (using older version of EditBin from some older version of VS/VC/SDK apparently works). Solution is to patch 'em back to 4.0, here is some code that will do that

HANDLE hfile = CreateFile(argv[1],GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,0,NULL);
if (hfile!=INVALID_HANDLE_VALUE)
{
	HANDLE hmapping = CreateFileMapping(hfile,0,PAGE_READWRITE,0,0,0);
	if (hmapping)
	{
		BYTE *file_base = (BYTE *)MapViewOfFileEx(hmapping,FILE_MAP_ALL_ACCESS,0,0,0,0);
		if (file_base)
		{
			IMAGE_OPTIONAL_HEADER *ioh = (IMAGE_OPTIONAL_HEADER *)(file_base + ((IMAGE_DOS_HEADER *)file_base)->e_lfanew + 4 + sizeof(IMAGE_FILE_HEADER));
			ioh->MajorOperatingSystemVersion = 4;
			ioh->MinorOperatingSystemVersion = 0;
			ioh->MajorSubsystemVersion = 4;
			ioh->MinorSubsystemVersion = 0;
			UnmapViewOfFile(file_base);
		}
		CloseHandle(hmapping);
	}
	CloseHandle(hfile);
}

Part 2 (Win9x): the CRT uses the Unicode / Widechar version of GetModuleHandle() in it's internal _crt_waiting_on_module_handle(). Here is a replacement that doesn't

extern "C" __declspec(noinline) HMODULE __cdecl _crt_waiting_on_module_handle(LPCWSTR szModuleName)
{
#define INCR_WAIT                          1000
#define _MAX_WAIT_MALLOC_CRT 60000
    char szModuleNameA[MAX_PATH];
    WideCharToMultiByte(CP_ACP,0,szModuleName,-1,szModuleNameA,_countof(szModuleNameA),NULL,NULL);
    unsigned long nWaitTime = INCR_WAIT;
    HMODULE hMod = GetModuleHandleA(szModuleNameA);
    while(hMod == NULL)
    {
        Sleep(nWaitTime);
        hMod = GetModuleHandleA(szModuleNameA);
        nWaitTime += INCR_WAIT;
        if(nWaitTime > _MAX_WAIT_MALLOC_CRT)
        {
                break;
        }
    }
    return hMod;
#undef INCR_WAIT
#undef _MAX_WAIT_MALLOC_CRT
}

Part 3 (Win9x): the CRT is incorrectly checking the return code of InitializeCriticalSectionAndSpinCount() on Win9x, and here is the fixed version of that function

extern "C" __declspec(noinline) int __cdecl __crtInitCritSecAndSpinCount (PCRITICAL_SECTION lpCriticalSection,DWORD dwSpinCount)
{
    int ret;
    __try {
        /*
         * Call the real InitializeCriticalSectionAndSpinCount
         */
        ret = InitializeCriticalSectionAndSpinCount(lpCriticalSection, dwSpinCount);
        if (GetVersion()&0x80000000)
            ret = TRUE; // when win9x assume true (else we would have thrown an exception - read the MSDN :-)
    }
    __except (_exception_code()== STATUS_NO_MEMORY ? EXCEPTION_EXECUTE_HANDLER:EXCEPTION_CONTINUE_SEARCH) {
        /*
         * Initialization failed by raising an exception, which is probably
         * STATUS_NO_MEMORY.  It is not safe to set the CRT errno to ENOMEM,
         * since the per-thread data may not yet exist.  Instead, set the Win32
         * error which can be mapped to ENOMEM later.
         */
        if (GetExceptionCode() == STATUS_NO_MEMORY) {
            SetLastError(ERROR_NOT_ENOUGH_MEMORY);
        }
        ret = FALSE;
    }
    return ret;
}

Quick set of notes on making use of this info in vs2008