Sorting Out the Confusion: 32- vs. 64-Bit, CLR vs. Native, C# vs. C++

2010-02-13

I’ve been trying to learn how things work on Windows based on whether you write code in C# or C++, target a 32- or 64-bit platform, and produce files with either native code or one of the CLR options. One of my focuses is the interaction between exes and dlls. I think I’ve got things mostly straightened out, so this is what I’ve learned.

First, the basics: a 32-bit platform can run 32-bit apps, but not 64-bit apps. A 64-bit platform can run either, but 32-bit apps run in an emulation environment called WOW64 (Windows on Windows 64). When Windows starts your app, it decides whether WOW64 is necessary. You can tell whether your app is running in WOW64 using this C++ code:

#include "stdafx.h"
#include <windows.h>

typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);

LPFN_ISWOW64PROCESS fnIsWow64Process;

BOOL isWow64() {
    BOOL ret = FALSE;
    fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(
        GetModuleHandle(TEXT("kernel32")), "IsWow64Process");

    if (NULL != fnIsWow64Process) {
        if (!fnIsWow64Process(GetCurrentProcess(), &ret)) {
            printf("Got some error\n");
        }
    }
    return ret;
}

int _tmain(int argc, _TCHAR* argv[]) {
    if (isWow64()) {
        printf("Running under WOW64.\n");
    } else {
        printf("NOT running under WOW64.\n");
    }
    scanf("press return");
    return 0;
}

It’s easy enough to call isWow64 from C#, like so:

[DllImport]("IsWow64Dll.dll")]
static extern bool isWow64();

static void Main(String[] args) {
    Console.WriteLine(isWow64().ToString());
    Console.ReadLine();
}

Visual Studio lets you build files for either 32- or 64-bit platforms. I’ve already written how to build for 32 or 64 bits in C++. C# actually provides three options: 32-bits, 64-bits, or “Any CPU.” We can use a tool called corflags to see what results we get depending on which option we choose. Corflags comes with Visual Studio and can be run by choosing the special DOS prompt command under Visual Studio in the Start menu. This is a little different from the regular DOS prompt: it has a specially-tailored environment for running Visual Studio’s command-line utilities. From there, you can ask corflags to report information about any exe or dll, like this:

C:\> corflags myapp.exe
Microsoft (R) .NET Framework CorFlags Conversion Tool.  Version  3.5.21022.8
Copyright (c) Microsoft Corporation.  All rights reserved.

Version   : v2.0.50727
CLR Header: 2.5
PE        : PE32
CorFlags  : 3
ILONLY    : 1
32BIT     : 1
Signed    : 0

We’re mostly interested in three values: PE, 32BIT, and ILONLY. There is also a line labelled “Signed,” which I’m not interested in right now. Finally, the “CorFlags” line appears to be a combination of the four other values.

PE specifies whether or not the file can run on 32-bit platforms. It is either PE32 or PE32+. A PE32+ file cannot run on a 32-bit machine.

Next there is the 32BIT flag. This is a little different from PE. If PE indicates whether your app can run as 32 bits, then 32BIT indicates whether it must run as 32 bits. If this flag is 0, your app can run on a 64-bit machine without WOW64. But if the flag is 1, then your app has to run under WOW64. Here is a table showing how the bits are set depending on your compiler’s /platform setting:

Compiler Option PE 32BIT
x86 PE32 1
Any CPU PE32 0
x64 PE32+ 0

From this table, you can see that the corflags example above is inspecting a C# app built for the x86 platform. Note that you could never have a file that is PE32+ with the 32BIT flag set, because then one flag would require 32 bits and the other 64.

To put all this together, a 32-bit machine can run anything with a PE set to PE32, but nothing with a PE of PE32+. A 64-bit machine can run your file in 64-bit mode as long as 32BIT is 0, but if 32BIT is 1 then it must use WOW64.

The ILONLY flag indicates that your file contains only MSIL opcodes (recently renamed to CIL), with no native assembly instructions. A C# app will always have this flag set (unless you use something like ngen to compile down to machine language—an approach with some distribution problems), but a C++ app’s setting depends on your compiler options (described below).

When it comes to loading dlls, these flags control whether your app loads the dll successfully or gets a BadImageFormatException. Basically, a 32-bit app can only load 32-bit dlls, and a 64-bit app can only load 64-bit dlls. But what about apps compiled as “Any CPU”? In that case, you can only load dlls matching whatever bitness you’re currently running as. Of course, if you’re running on a 32-bit machine, there is no complication, because everything is 32-bit already.

But on a 64-bit machine, you may have problems. Windows will not use WOW64 for your app, because it claims to support 64-bit operation. But if your app has a dependency on a 32-bit dll, then you’ll get a BadImageFormatException, because the 32-bit dll only works in WOW64. The choice to use WOW64 happens only when starting your app. You can’t run an app natively and load just the dlls in WOW64. So you get the exception.

The solution is to tell Windows that your app must start in WOW64 from the beginning. You should probably do this by building your app for x86, not Any CPU, but if that is somehow a problem (e.g. you don’t have the code), then you can use corflags to set the 32BIT flag. You just type something like this:

corflags /32BIT+ myapp.exe

For C++ applications, you can do something similar with the linker’s /clrimagetype flag.

Another choice, at least when writing in C++, is how to support the CLR. You can choose among four options: native (the default), /clr, /clr:pure, and /clr:safe. The first one is simple enough: you get a file with machine language instructions. The other three give you a file that is partially or entirely composed of MSIL. Using /clr will produce a CLR header and mostly MSIL code, but with some native code mixed in. Specifically, you get native data types but MSIL functions, unless the function uses something unsupported like function pointers. (Everyday pointers to data are supported.) You can also use #pragma unmanaged to force native code. Because these files have some native code, they must be built for a specific platform, either x86 or x64.

The /clr:pure option does what it sounds like: it gives you a file of entirely MSIL. Nonetheless, it must be built for either x86 or x64. This option is said to be equivalent to a C# project with unsafe code.

Microsoft’s documentation on the /clr and /clr:pure flags says that they can only produce x86 files, but my tests prove this to be false. If I build the C++ version of the WOW64-tester, using x64 and /clr compilation options, then it reports that it is not running in WOW64. So apparently you can in fact produce x64 applications with these options.

The last one, /clr:safe, enforces code that is verifiably type-safe—but I’m not sure what all that means. I’ve read that if you use this option, your file can run on any platform, like building as Any CPU in C#. This option requires that you use Microsoft’s C++/CLI language, formerly known as Managed C++. I know nothing about this, but people say it’s virtually a new language. I tried to build a Hello World app with printf and got innumerable compile errors, so I wasn’t able to run any tests on what this option produces.

There is also a /clr:oldSyntax option, which is like /clr:safe but with the old Managed C++ syntax rather than C++/CLI. Since Managed C++ is deprecated, I’m not sure why you’d use this for new code.

I don’t know what the /clr* options mean for P/Invoke. If I build a dll with /clr or /clr:pure, does that mean I can call its exported functions from C# without a DllImport statement? I haven’t tried. Using DllImport on these dlls doesn’t cause problems, though.

You can use a tool called dumpbin to see which /clr options were used to produce a given file. Dumpbin comes with Visual Studio and runs from the command-line:

dumpbin /CLRHEADER myapp.exe

This will print (among other things) a Flags value, which is 0 if the file was build with /clr, 1 with /clr:safe, and 3 with /clr:pure.

I’m also curious about the interaction between WOW64 and the CLR. If I run a 32-bit C# app on x64, then which comes first: WOW64 or the CLR? Is there a 64-bit CLR that can JIT-compile to either 32- or 64-bit code? Or do I have two CLRs, one for 32 bits and one for 64, and the former runs under WOW64? I suspect the answer is the latter, but I’m not sure how to tell. Either way my code is running in WOW64, so the check I described above won’t tell me anything.

I created a table to keep track of the data from all my tests. Here it is:

exe machine type binary contents managed? contains assembly? can run on x86? can run on x64? can use x86 C# dll? can use x64 C# dll? can use "Any CPU" C# dll? can use x86 C++ native dll? can use x64 C++ native dll? can use x86 C++ /clr dll? can use x64 C++ /clr dll? can use x86 C++ /clr:pure dll? can use x64 C++ /clr:pure dll?
x86 C# exe i386 MSIL yes yes yes, in CLR yes, in WOW64+CLR yes no yes yes no yes no yes no
x64 C# exe x64 MSIL yes yes no yes, in CLR no yes yes no2 yes no yes no yes
"Any CPU" C# exe i386 MSIL yes yes yes, in CLR yes, in CLR only on x861 only on x64 yes only on x861 only on x64 only on x86 only on x64 only on x86 only on x64
x86 C++ exe i386 asm no no yes, natively yes, in WOW64 ? ? ? ? ? ? ? ? ?
x64 C++ exe x64 asm no no no yes, natively ? ? ? ? ? ? ? ? ?
x86 C++ /clr exe i386 MSIL, mostly ? ? yes, in CLR yes, in WOW64+CLR ? ? ? ? ? ? ? ? ?
x64 C++ /clr exe x64 MSIL, mostly ? ? no yes, in CLR ? ? ? ? ? ? ? ? ?
x86 C++ /clr:pure exe i386 MSIL yes ? yes, in CLR yes, in WOW64+CLR ? ? ? ? ? ? ? ? ?
x64 C++ /clr:pure exe x64 MSIL yes ? no yes, in CLR ? ? ? ? ? ? ? ? ?

1

There may be some option like the linker’s /CLRHEADER for C# apps.

2

Can’t run on x86, and won’t be in WOW64 on x64. But see note 1.

There are still some gaps in this table. I’m not that concerned about the interactions between C++ exes and C++ dlls, so I’ve left those cells blank. I’ve also left some cell blanks regarding when C++ files are managed/unmanaged and when they contain an assembly. If I figure any of this out, I’ll update the table.

One final notable tool is ildasm (IL-disassembler), which also comes with Visual Studio. This lets you inspect the IL of an exe or dll. Most of it is over my head, but it’s intetesting to see what your code becomes.

blog comments powered by Disqus Prev: C# XmlTextReader Tutorial Next: Decline in Inverse and Leveraged ETFs