Understanding .NET application options for 32 and 64-bit systems

Introduction

Around mid-nineties, the migration from 16-bit systems to
32-bit systems was in full swing. The benefits were very
clear, but unfortunately, the move caused hair pulling for
many developers as they needed to learn new memory
models, new API functions and changed pointer
arithmetic.

In the present day, we are facing a similar situation
when moving from 32-bit to 64-bit systems. Luckily, this
change is much more modest, and for many applications, not
much needs to be done to keep the 32-bit application happily
running on a 64-bit system. Because of the virtual machine
technology and the way CLR (Common Language Runtime) is
written, your .NET application might even automatically
convert itself to a 64-bit application.

However, to break through the 2 GB memory limit or use
native code or components outside the managed code barrier,
you need to pay attention to the way you write your
code.

In this article, you are going to learn the basics of
moving your ,.NET & C# applications to 64-bit systems. Along
the way, you are also going to learn a bit about memory
management, code compatibility, and discover migration
tips.

Note that this article uses the terms “32-bit system” and
“64-bit system” in a specific manner. Here, these terms are
used to indicate the operating system bitness, and not the
bits supported by the computer hardware. Technically
speaking, it is perfectly possible to install a 32-bit
operating system on a PC capable of running 64-bit operating
systems. In this article, bits refer to the installed
architecture of the operating system.

The basics of application compatibility

Because the number of available Windows applications is
enormous, a new Windows version cannot simply stop running
older applications. The same applies when you migrate
upwards in the number of bits in the system. Thus, a regular
32-bit application can in many cases continue to run on a
64-bit system without changes to the application code.

When installing, for instance, a 64-bit Windows 7 (Figure
1), the DLLs that implement the Windows programming
interfaces are natively 64-bit. This might lead you to think
that 32-bit applications would not be able to run, but in
fact they can. This is because the operating system supports
a feature called Windows-on-Windows (WoW64), which simulates
a 32-bit Windows installation inside a 64-bit OS. Because of
this, all the 32-bit applications think that they are
running just as before, and in many cases work right out of
the box.



Figure 1. System information about a 64-bit Windows 7
installation after pressing Win+Pause/Break.

All this applies well to native Windows applications, but
.NET applications are somewhat different by default. Because
of an additional layer of abstraction between the
application and the operating system, the .NET runtime can
decide how the intermediate language (IL) instructions are
just-in-time (JIT) compiled to native code. Depending on the
Windows operating system bitness, the CLR compiles the code
to be either 32-bit or 64-bit native code. Thus, a .NET
application compiled with the default settings will
automatically reflect the number of bits in the underlying
OS.

Although this is a very convenient behavior for the
developer, it is not always the optimal one. Instead, you
might wish to force your application to be a 32-bit or 64-
bit one, depending on the circumstances. For instance, you
might have dependencies in your application for native code
components that are of certain number of bits. To be able to
use these from your application, you usually must force an
equal number of bits in your .NET application.

To select the number of bits you want in your
application, it is best to go Microsoft Visual Studio’s
project options. For any application type (Figure 2 shows
the properties of a Windows Presentation Foundation (WPF)
application), Visual Studio shows in the project properties
window (opened through the Project menu) a tab called
Build. This tab in turn contains an option
called Platform target. By default, the option is set to
“Any CPU”, meaning that the application will be compiled to
native code depending on the number of bits in the operating
system.


Figure 2. Visual Studio project
options show the Platform target setting under the Build
tab.

The other settable options are “x86” and “x64” for 32-
and 64-bit builds, respectively. There’s also the option to
compile to the Itanium platform, but this platform is rare,
and thus not the focus here. Remember that if you select for
instance x64 as the platform target and try to run the
application on a 32-bit system, the application won’t run
(Figure 3).


Figure 3. A 32-bit Windows 7 cannot
run 64-bit applications.

Briefly about data types in .NET applications

If you as a .NET developer have experience in native code
development as well as .NET application development, you
might be interested in knowing what happens to your data
types in .NET applications. In native code for example, an
“integer” might be four bytes on a 32-bit system, but eight
bytes on a 64-bit system, depending on your programming
language.

In .NET applications, things are simpler. The basic
numeric data types are always a certain number of bits: for
instance, an int is always 32-bits and a
short is always 16-bits. This makes programming
easier when moving from 32-bit systems to 64-bit. This is
different from many native programming languages where the
basic data types like int often change their
size when moving from 32-bit to 64-bit.

However, if you are working with native code
technologies, such as Platform Invoke (P/Invoke), COM, or
native code DLLs, you will need to understand the details
about variable sizes. To help you in this process, .NET
defines a special type called IntPtr.

This type, defined as a structure, is platform-specific.
This means that its size changes depending on the platform
target: it is four bytes on 32-bit builds and eight on 64-
bit ones. You can use this type to conveniently store a
pointer or a Windows handle, and have the same code work in
both 32 and 64-bit systems. If you would try to use a .NET
int value instead, you would see your code
working on an x86 system, but failing on an x64 system.

About memory allocation limits in .NET applications

One of the primary reasons of moving to 64-bit
architecture is the ability to use more memory. By default,
32-bit Windows applications are limited to 2 GB of memory
(virtual address space). For many applications, this is
enough, but high-end applications or those manipulating
large amounts of data (be it images, video, statistical
input or SQL databases) can run faster, if they can process
larger amounts of memory without dividing the data into
smaller chunks.

For .NET developers, understanding low-level memory
management hasn’t been necessary, as the CLR handles all the
rudimentary work. Instead, it is enough to simply
instantiate objects, and let the runtime handle the rest.
The same applies in 64-bit systems, but even if you are
running your .NET application on a 64-bit system, you cannot
simply start allocating huge, multi-gigabyte arrays right
away.

Instead, there’s a limit on the size of any single object
in .NET. For instance, you cannot allocate a byte array
larger than approximately two gigabytes minus 64K. This
limitation applies to both 32-bit and 64-bit applications.
It would be great if you could allocate multi-gigabyte
arrays in 64-bit applications, but unfortunately, this is
not the case.

However, when your application is a 64-bit one (or runs
on a 64-bit system when built with the Any CPU platform
target), you can happily allocate multiple two gigabyte
arrays without any problem, provided of course that there is
enough system memory available.

Bear also in mind that the size limitation only applies
to single objects. If you have an object that merely holds
references (pointers) to other objects, like a list or a
collection, you don’t need to limit yourself to the roughly
two gigabyte limit.

Calling native code from .NET applications

One of the great things about .NET application
compatibility is that if you are using mainly .NET class
library features and the Any CPU platform target, then you
often don’t need to worry about the operating system being a
32-bit or a 64-bit one. However, if you need to call COM
objects, native DLL functions or the like, then you need to
pay attention to the number of bits your application is
built with.

For instance, assume you had written a 32-bit DLL that
contains some legacy code that you don’t want to or don’t
have the time to port to managed .NET code. If you would run
your .NET application (built with the default settings as an
Any CPU application) on a 64-bit system, your application
would run as a 64-bit application, but the DLL would still
be 32-bit. This will cause trouble, as a
BadImageFormat exception will be raised with
the message “An attempt was made to load a program with an
incorrect format” (Figure 4).



Figure 4. A 64-bit process cannot load a 32-bit DLL directly.

In these situations, it is best to specifically select
the correct platform target in Microsoft Visual Studio’s
project options. If your native code DLL would only be for
instance 32-bit, then select x86 as the platform target.
This will solve the compatibility issue, but on the other
hand, you will lose the benefits of the additional memory a
64-bit system could provide.

If you can compile the DLL into a 64-bit version, then
select x64 as the target for your .NET application. This can
lead you to distribute two editions of your application.
Alternatively, you could also dynamically load the correct
DLL version based on the number of bits in the system. To
detect the number of bits, you could use the methods shown
in the next section.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read