Thursday 6 October 2011

32-bit and 64-bit versions of Windows C++ application

How to set compiler and linker options in order to build some C++ application for 64-bit Windows? Sample application I will test this on is one that detects two things that should not be mixed:
  • detecting which processor architecture application has been compiled for (performed in compile time)
  • detecting which processor architecture application is running on (performed in run-time)
There is no portable way of performing these checks.

Compiler's target architecture can be detected by checking which pre-defined symbols (macros) have automatically been defined by compiler (this is compiler specific! - different compilers use different macros). Some Microsoft-specific are:

_WIN32 - defined for applications for Win32 and Win64. Always defined.
_WIN64 - defined for applications for Win64.
_M_AMD64 - defined for x64 processors
_M_X64 - defined for x64 processors
_M_IX86 - defined for x86 processors
_M_IA64 - defined for Itanium Processor Family 64-bit processors

These macros determine target processor - one that we are building application for (not processor we are building on). We don't need to add them manually as compiler will define them: _WIN32 and _M_IX86 for 32-bit applications and _WIN64, _M_AMD64, _M_X64 and _M_IA64 for 64-bit ones.

Another test can be checking the value of the sizeof(void*). It is 4 bytes in applications compiled for 32-bit architectures and 8 bytes in 64-bit applications, regardless on the processor architecture application is running on.

Host's processor architecture can be obtained in run-time in various ways, of which some of them (on Windows) are:
  •  __cpuid
  • check PROCESSOR_ARCHITECTURE and PROCESSOR_ARCHITEW6432 environment variables (or read value of PROCESSOR_ARCHITECTURE in registry on path HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment)
  • Use GetSystemInfo() or GetNativeSystemInfo()
main.cpp uses methods described above:

#include <windows.h>
#include <iostream>
#include <string>
#include <intrin.h>
using namespace std;

void CheckIs64BitAvailable()
{
bool b64Available = false;
int CPUInfo[4] = {0};
        __cpuid(CPUInfo, 0);
        b64Available = (CPUInfo[3] & 0x20000000) || false;

if(b64Available)
{
cout << "64-bit processor" << endl;
}
else
{
cout << "32-bit processor" << endl;
}
}

void CheckNativeSystemProcessorArchitecture()
{
SYSTEM_INFO si;
memset(&si, 0, sizeof(si));
GetNativeSystemInfo(&si);

switch(si.wProcessorArchitecture)
{
case PROCESSOR_ARCHITECTURE_AMD64:
cout << "PROCESSOR_ARCHITECTURE_AMD64 - x64 (AMD or Intel)" << endl;
break;
case PROCESSOR_ARCHITECTURE_IA64:
cout << "PROCESSOR_ARCHITECTURE_IA64 - Intel Itanium-based" << endl;
break;
case PROCESSOR_ARCHITECTURE_INTEL:
cout << "PROCESSOR_ARCHITECTURE_INTEL - x86" << endl;
break;
case PROCESSOR_ARCHITECTURE_UNKNOWN:
cout << "PROCESSOR_ARCHITECTURE_UNKNOWN - Unknown architecture" << endl;
break;
}
}

void CheckSystemProcessorArchitecture()
{
cout << "Calling GetSystemInfo()..." << endl;

SYSTEM_INFO si;
memset(&si, 0, sizeof(si));
GetSystemInfo(&si);

switch(si.wProcessorArchitecture)
{
case PROCESSOR_ARCHITECTURE_AMD64:
cout << "PROCESSOR_ARCHITECTURE_AMD64 - x64 (AMD or Intel)" << endl;
break;
case PROCESSOR_ARCHITECTURE_IA64:
cout << "PROCESSOR_ARCHITECTURE_IA64 - Intel Itanium-based" << endl;
break;
case PROCESSOR_ARCHITECTURE_INTEL:
cout << "PROCESSOR_ARCHITECTURE_INTEL - x86" << endl;
break;
case PROCESSOR_ARCHITECTURE_UNKNOWN:
cout << "PROCESSOR_ARCHITECTURE_UNKNOWN - Unknown architecture" << endl;
break;
}
}

void CheckEnvVariable_PROCESSOR_ARCHITECTURE()
{
char* pszProcArch = getenv("PROCESSOR_ARCHITECTURE");
if(pszProcArch)
{
cout << "PROCESSOR_ARCHITECTURE (environment variable): " << string(pszProcArch) << endl;
}
else
{
cout << "PROCESSOR_ARCHITECTURE (environment variable) not found" << endl;
}
}

void CheckEnvVariable_PROCESSOR_ARCHITEW6432()
{
// PROCESSOR_ARCHITEW6432 is defined only in WoW64 where reports the original native processor architecture
char* pszProcArch = getenv("PROCESSOR_ARCHITEW6432");
if(pszProcArch)
{
cout << "PROCESSOR_ARCHITEW6432 (environment variable): " << string(pszProcArch) << endl;
}
else
{
cout << "PROCESSOR_ARCHITEW6432 (environment variable) not found" << endl;
}
}

void CheckRegVal_PROCESSOR_ARCHITECTURE()
{
HKEY hKey;
DWORD dwBuffSize = 1023;
DWORD dwType;
char szValue[1024] = {0};

LONG lErrorCode = RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment",
0,
KEY_QUERY_VALUE,
&hKey);

if(lErrorCode!= ERROR_SUCCESS)
{
cout << "RegOpenKeyEx() failed. Error: " << lErrorCode << endl;
return;
}
 
RegQueryValueEx(
hKey,
"PROCESSOR_ARCHITECTURE",
NULL,
&dwType,
(LPBYTE)szValue,
&dwBuffSize);

cout << "PROCESSOR_ARCHITECTURE (registry value): " << string(szValue) << endl;

RegCloseKey(hKey);        
}

int main()
{
cout << "sizeof(void*) = " << sizeof(void*) << " [bytes]"<< endl;

CheckIs64BitAvailable();
CheckRegVal_PROCESSOR_ARCHITECTURE();

// Defined for applications for Win32 and Win64. Always defined.
#ifdef _WIN32
cout << "_WIN32 defined" << endl;
#endif

// _WIN64 is defined for applications for Win64 (they can run only on Win64)
#ifdef _WIN64
cout << "_WIN64 defined => target architecture is Win64; Process is running on native Win64" << endl;
CheckSystemProcessorArchitecture();
CheckEnvVariable_PROCESSOR_ARCHITECTURE();
#else
cout << "_WIN64 is not defined => target architecture is Win32" << endl;

BOOL bIs64 = FALSE;
IsWow64Process(GetCurrentProcess(), &bIs64);

if(bIs64)
{
cout << "Process is running under WoW64" << endl;
CheckNativeSystemProcessorArchitecture();
CheckEnvVariable_PROCESSOR_ARCHITEW6432();
}
else
{
cout << "Process is running on Win32" << endl;
CheckSystemProcessorArchitecture();
CheckEnvVariable_PROCESSOR_ARCHITECTURE();
}
#endif

// WIN32 is defined outside compiler - by the SDK or the build environment
#ifdef WIN32
cout << "WIN32 defined" << endl;
#endif

// WIN64 is defined outside compiler - by the SDK or the build environment
#ifdef WIN64
cout << "WIN64 defined" << endl;
#endif

// Pre-defined Architecture Macro: Intel x86; Defined for x86 processors
#ifdef _M_IX86
cout << "_M_IX86 defined: " << _M_IX86 << endl;
#endif

// Pre-defined Architecture Macro: AMD64; Defined for x64 processors.
#ifdef _M_X64
cout << "_M_X64 defined: " << _M_X64 << endl;
#endif

// Defined for x64 processors.
#ifdef _M_AMD64
cout << "_M_AMD64 defined: " << _M_AMD64 << endl;
#endif

// Pre-defined Architecture Macro: Intel Architecture-64 (Defined for Itanium Processor Family 64-bit processors.)
#ifdef _M_IA64
cout << "_M_IA64 defined: " << _M_IA64 << endl;
#endif

return 0;
}

This is application's output when run on 32-bit Windows (Win 7):
..\Release>ProcArchTest_x86.exe
sizeof(void*) = 4 [bytes]
32-bit processor
PROCESSOR_ARCHITECTURE (registry value): x86
_WIN32 defined
_WIN64 is not defined => target architecture is Win32
Process is running on Win32
Calling GetSystemInfo()...
PROCESSOR_ARCHITECTURE_INTEL - x86
PROCESSOR_ARCHITECTURE (environment variable): x86
WIN32 defined
_M_IX86 defined: 600

C:\DEVELOPMENT\RESEARCH\C++\ProcessorBitTest\Release>

This is application's output when run on 64-bit Windows (Win 7 emulated on VirtualBox):
C:\Users\Bojan\Desktop>ProcArchTest_x86.exe
sizeof(void*) = 4 [bytes]
32-bit processor
PROCESSOR_ARCHITECTURE (registry value): AMD64
_WIN32 defined
_WIN64 is not defined => target architecture is Win32
Process is running under WoW64
PROCESSOR_ARCHITECTURE_AMD64 - x64 (AMD or Intel)
PROCESSOR_ARCHITEW6432 (environment variable): AMD64
WIN32 defined
_M_IX86 defined: 600

C:\Users\Bojan\Desktop>

Now, let us build this application for 64-bit Windows. In Visual Studio, we need to create a new build platform: Build -> Configuration Manager -> Active Solution Platform, New; select "x64" and copy settings from Win32.

Let us compare compiler options for 32-bit and 64-bit Release configurations in order to see what are the differences:

Compiler options for Win32 Release:
/Zi /nologo /W3 /WX- /O2 /Oi /Oy- /GL /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /Gm- /EHsc /MT /GS /Gy /fp:precise /Zc:wchar_t /Zc:forScope /Fp"Release\ProcArchTest_x86.pch" /Fa"Release\" /Fo"Release\" /Fd"Release\vc100.pdb" /Gd /analyze- /errorReport:queue

Compiler options for x64 Release:
/Zi /nologo /W3 /WX- /O2 /Oi /GL /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /Gm- /EHsc /MT /GS /Gy /fp:precise /Zc:wchar_t /Zc:forScope /Fp"x64\Release\ProcArchTest_x64.pch" /Fa"x64\Release\" /Fo"x64\Release\" /Fd"x64\Release\vc100.pdb" /Gd /errorReport:queue

We can see that all options are the same except in case of /Oy and /analyze which are available only for x86 compilers.

What about linker options?

Linker options for Win32 Release:
/OUT:"..\Release\ProcArchTest_x86.exe" /INCREMENTAL:NO /NOLOGO "kernel32.lib" "user32.lib" "gdi32.lib" "winspool.lib" "comdlg32.lib" "advapi32.lib" "shell32.lib" "ole32.lib" "oleaut32.lib" "uuid.lib" "odbc32.lib" "odbccp32.lib" /MANIFEST /ManifestFile:"Release\ProcArchTest_x86.exe.intermediate.manifest" /ALLOWISOLATION /MANIFESTUAC:"level='asInvoker' uiAccess='false'" /DEBUG /PDB:"..\ProcessorBitTest\Release\ProcArchTest_x86.pdb" /SUBSYSTEM:CONSOLE /OPT:REF /OPT:ICF /PGD:"..\ProcessorBitTest\Release\ProcArchTest_x86.pgd" /LTCG /TLBID:1 /DYNAMICBASE /NXCOMPAT /MACHINE:X86 /ERRORREPORT:QUEUE

Linker options for x64 Release:
/OUT:"..\x64\Release\ProcArchTest_x64.exe" /INCREMENTAL:NO /NOLOGO "kernel32.lib" "user32.lib" "gdi32.lib" "winspool.lib" "comdlg32.lib" "advapi32.lib" "shell32.lib" "ole32.lib" "oleaut32.lib" "uuid.lib" "odbc32.lib" "odbccp32.lib" /MANIFEST /ManifestFile:"x64\Release\ProcArchTest_x64.exe.intermediate.manifest" /ALLOWISOLATION /MANIFESTUAC:"level='asInvoker' uiAccess='false'" /DEBUG /PDB:"..\ProcessorBitTest\x64\Release\ProcArchTest_x64.pdb" /SUBSYSTEM:CONSOLE /OPT:REF /OPT:ICF /PGD:"..\ProcessorBitTest\x64\Release\ProcArchTest_x64.pgd" /LTCG /TLBID:1 /DYNAMICBASE /NXCOMPAT /MACHINE:X64 /ERRORREPORT:QUEUE

We can see that linker options differ only in the value of /MACHINE option.

NOTE: /MACHINE option does not need to be set at all! Compiler inserts information about target architecture in OBJ files' headers. OBJ files created with Microsoft compiler have PE/COFF format which is described in Microsoft PE and COFF Specification. At the beginning of an object file, or immediately after the signature of an image file, is a standard COFF file header. Its first field is "Machine" whose value is the number that identifies the type of target machine (CPU type). Some values are: IMAGE_FILE_MACHINE_AMD64 (x64), IMAGE_FILE_MACHINE_I386 (Intel 386 or later processors and compatible processors - x86), IMAGE_FILE_MACHINE_IA64 (Intel Itanium processor family). Linker reads that information. If you compile your app with $(VCInstallDir)bin\x86_amd64\cl.exe linker will create amd64 binary even with /MACHINE Not Set! Cross-compiling with Microsoft (Visual Studio) tools is all about using correct compiler and linker! Compiler and linker options can actually be same for both 32-bit and 64-bit builds! When building 64-bit application in Visual Studio, just make sure that Executable Directories has (cl.exe and link.exe) paths in this order $(VCInstallDir)bin\x86_amd64;$(VCInstallDir)bin; and that AMD paths are listed first in Include and Library Directories (but all this should be done automatically upon creation of x64 project!).

Let us continue with analysis.

Although names of some libraries end with "32" for both x86 and x64 configurations, their different (32-bit or 64-bit) versions are actually used in different builds. Same for the compiler and linker - please read my article "NMAKE and its environment".

Let us try to run now our 64-bit application on 32-bit Windows OS:
..\x64\Release>ProcArchTest_x64.exe
This version of ..\x64\Release\ProcArchTest_x64.exe is not compatible with the
version of Windows you're running. Check your computer's system information
to see whether you need a x86 (32-bit) or x64 (64-bit) version of the program,
and then contact the software publisher.

C:\DEVELOPMENT\RESEARCH\C++\ProcessorBitTest\x64\Release>

This was expected as 64-bit application cannot run on 32-bit operating systems.
What about running it in its natural, 64-bit, enviroment - on 64-bit OS?

C:\Users\Bojan\Desktop>ProcArchTest_x64.exe
sizeof(void*) = 8 [bytes]
32-bit processor
PROCESSOR_ARCHITECTURE (registry value): AMD64
_WIN32 defined
_WIN64 defined => target architecture is Win64; Process is running on native Win
64
Calling GetSystemInfo()...
PROCESSOR_ARCHITECTURE_AMD64 - x64 (AMD or Intel)
PROCESSOR_ARCHITECTURE (environment variable): AMD64
WIN32 defined
_M_X64 defined: 100
_M_AMD64 defined: 100

C:\Users\Bojan\Desktop>

Conclusion:
  • application targets 64-bit architecture if _WIN64, _M_X64 and/or _M_AMD64 are defined and/or if sizeof(void*) is 8
  • reliable way of getting the architecture of the host processor in runtime is reading PROCESSOR_ARCHITECTURE value from registry or using GetSystemInfo()/GetNativeSystemInfo()
Advice:
Don't rely on WoW64 as it might not be enabled/installed on the target machine (e.g. WoW64 is optional on Windows Server 2003 R2). WoW64 is intended to support legacy 32-bit applications anyway. Build new applications for both x86 and x64 architectures. This way you can avoid some potential errors in applications that can occur due to directory and registry redirections under WoW64.

Links and references:

IsWow64Process function
64-bit (Wikipedia)
WoW64 (Wikipedia)
Use Visual Studio to build 64-bit application (MSDN blog)
Everything You Need To Know To Start Programming 64-Bit Windows Systems
Visual Studio Development Environment 64-Bit Support
HOWTO: Detect Process BitnessHow to: Configure Projects to Target Platforms (MSDN)
How to: Configure Visual C++ Projects to Target 64-Bit Platforms (MSDN)
How to determine whether a computer is running a 32-bit version or 64-bit version of the Windows operating system (MSDN)
Predefined Macros (MSDN)
You already know what your target architecture is (or at least you should)
How to detect programmatically whether you are running on 64-bit Windows
Detecting CPU architecture compile-time
Detect system architecture (x86/x64) while running
#ifdef for 32-bit platform
Determining 64-bit vs. 32-bit Windows
Use Visual Studio to build 64-bit application
Seven Steps of Migrating a Program to a 64-bit System

No comments: