It was recently pointed out to me that a simple “hello world” style application built with Open Watcom C/C++ 1.9 does not run on Win32s version 1.30, even though the same executable runs just fine on Windows NT 3.51, Windows 95, or Windows 10.
More specifically, the program crashes rather early on Win32s. With the help of map files and source code, I established that the crash occurs in an internal function called
__setenvp, which tries to dereference a null pointer stored in an internal variable
_RWD_Envptr variable is filled in by the
GetEnvironmentStrings API in the C runtime startup code. The
GetEnvironmentStrings API call ends up importing
GetEnvironmentStringsA from KERNEL32.DLL. And clearly
GetEnvironmentStringsA is failing on Win32s, although it works just fine on NT and Win9x.
Further probing revealed that the
GetEnvironmentStrings API has curious history. On Windows NT 3.1, there was only
GetEnvironmentStrings (no A or W suffix). On all later Win32 implementations, starting with NT 3.5, there’s
GetEnvironmentStringsW, as well as
On NT 3.1, there was no
FreeEnvironmentStrings, presumably because
GetEnvironmentStrings returned a pointer to existing memory that couldn’t be freed (and would be freed at process termination anyway). On NT 3.5,
GetEnvironmentStringsA converts the strings provided by
GetEnvironmentStringsW and allocates memory for the converted strings, so there is something to free.
A quick experiment with Microsoft Visual Studio 4.0 showed that a test application does run on Win32s; reading MSVC 4.0 runtime source code also revealed that Microsoft calls
GetEnvironmentStringsA and immediately terminates the process if
GetEnvironmentStringsA fails. So… how can that work on Win32s?
Examining the EXE file produced by MSVC 4.0 revealed that it imports
GetEnvironmentStrings and not
GetEnvironmentStringsA. Changing the Open Watcom kernel32.lib import library to make
GetEnvironmentStringsA an alias of
GetEnvironmentStrings made the application work on Win32s. But why?
A closer look at W32SCOMB.DLL shipped with Win32s showed the cause of the odd Win32s behavior. Although W32SCOMB.DLL exports all of
GetEnvironmentStringsW, the latter two are stubs which always fail, and only
GetEnvironmentStrings with no suffix actually does something useful. That seems like a bug in Win32s—
GetEnvironmentStringsA should have been an alias of
The mess was most likely caused by a design defect in Windows NT 3.1. The plain
GetEnvironmentStrings function probably should never have existed, only
GetEnvironmentStringsW, as is the case with other APIs. Windows NT 3.5 corrected the oversight, but its KERNEL32.DLL still had to export the suffix-free
GetEnvironmentStrings—otherwise almost all existing applications would have been broken.
Win32s tracked the development of Windows NT, therefore it implemented
GetEnvironmentStrings, and initially only that. Win32s version 1.20 (1994) added
GetEnvironmentStringsW, but only as dummies. As mentioned above, making
GetEnvironmentStringsA always fail was arguably wrong… but wasn’t noticed because Microsoft’s programs did not use
At least up to and including MSVCRT40.DLL, Microsoft’s runtime DLLs only imported
GetEnvironmentStrings. That also illustrates why any reasonable Win32 implementation needs to provide the
GetEnvironmentStrings import and not just
GetEnvironmentStringsA; if it didn’t, quite a few older applications would break because they need the suffix-free
Win32 SDK Details
As mentioned above, tweaking the kernel32.lib import library is one way to work around the problem with
GetEnvironmentStringsA on Win32s. But that’s not what Microsoft’s SDK does.
Here is how WINBASE.H in the NT 3.5 SDK defined the then-new
WINBASEAPI BOOL WINAPI FreeEnvironmentStringsA(LPSTR); WINBASEAPI BOOL WINAPI FreeEnvironmentStringsW(LPWSTR); #ifdef UNICODE #define FreeEnvironmentStrings FreeEnvironmentStringsW #else #define FreeEnvironmentStrings FreeEnvironmentStringsA #endif // !UNICODE
That’s the usual way of dealing with Unicode APIs. Function prototypes have ‘A’ and ‘W’ suffix, and a suffix-less macro is defined to map to one or the other.
But that’s not how
GetEnvironmentStrings was dealt with:
WINBASEAPI LPSTR WINAPI GetEnvironmentStrings(VOID); WINBASEAPI LPWSTR WINAPI GetEnvironmentStringsW(VOID); #ifdef UNICODE #define GetEnvironmentStrings GetEnvironmentStringsW #else #define GetEnvironmentStringsA GetEnvironmentStrings #endif // !UNICODE
The non-Unicode function prototype has no suffix, and the macro is “backwards”, mapping the ‘A’ function to the suffix-less original. Thus when the Microsoft runtime calls
GetEnvironmentStringsA, the compiler ends up generating a call to
GetEnvironmentStrings instead. This oddity persists to the present day and even Windows 10 SDK headers handle
GetEnvironmentStrings the same way.
Moral of the story? Changing operating system APIs is a messy business.