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

WEBINAR:On-Demand

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.

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.

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

WEBINAR:On-Demand

Detecting the number of bits in the system

Especially when working with native/unmanaged code, it is
beneficial to know how many bits the operating system runs
natively with. Although there isn't a Windows API function
or a .NET class library method to answer the question in a
clean and simple manner, there are many ways to indirectly
detect the number of bits in the system. Let's start with
some of the Windows API functions available for the
task.

If your application is a 32-bit one (built with the x86
platform target), then you can use the IsWow64Process API
function to detect if your application is running as a 32-
bit process under a 64-bit system under the Windows-on-
Windows (WoW64) feature. The presence of WoW64 indicates
that the operating system is a 64-bit one. If the process is
running under WoW64, then the return value of the function
is true; it is false otherwise. Here is an example
implementation:

Although the IsWow64Process API function is
convenient, it won't help much if your .NET application is
built with the Any CPU or x64 platform targets. In these
cases, you could use the GetSystemInfo API
function. This function returns a structure of details about
memory and the processor(s) in the system. A field named
lpMaximumApplicationAddress returns the highest
pointer value for a process' virtual address space.

For 32-bit applications, this value is by default roughly
2 GB (2^31), but even with the large address space
extensions enabled (through the /3GB boot time switch, to
enable a feature called 4 GT RAM Tuning), it is never above
4 GB (2^32). For 64-bit applications, the value depends on
the OS version and edition, but is in the range of 8 TB
(2^43).

Thus, you could check to see if the value is larger than
4 GB, and if so, you can be pretty confident that the system
is a 64-bit one if the platform target is Any CPU:

With managed .NET code alone, you can use similar
indirect methods to detect the number of bits in the system.
Probably the easiest way to do this is to check the size of
the IntPtr data type. This type reflects the
native pointer size of the system, and is four on a 32-bit
system and eight on a 64-bit system. Again for the Any CPU
platform target:

Another .NET based option would be to check environment
variables. Specifically, the variable named
PROCESSOR_ARCHITECTURE usually indicates the correct system
architecture. On a 32-bit system, the value is "x86", and on
a 64-bit system, it is "AMD64":

Finally, there are certain registry keys that are usually
only present on 64-bit Windows installations. Then, you
could check for the existence of these keys from code. For
instance, the registry key
"HKEY_LOCAL_MACHINE\Software\Wow6432Node" exists only on 64-
bit systems (on 32-bit systems, this key simply isn't
there). You could use the following code to check for the
key (this works regardless of the platform target
setting):

As you can see, all these methods are indirect and
approximate at best, and prone to failures. However, they
work well in most situations, and thus could be used with
reasonable confidence. It is also worth remembering that the
need to detect system bits is transient in nature: in the
future, most systems will be 64-bit ones, and thus you could
only deliver a 64-bit version of your applications by
setting the platform target property to x64. Then, Windows
itself will make sure your application won't run on any 32-
bit system.

Conclusion

Moving .NET applications to 64-bit systems is generally
easy. If you are using only managed class library features,
then in the best case, you would not need to do a thing to
make sure your application runs properly on any 64-bit
system.

However, as your applications become more complex, it is
likely that you need to pay attention to whether your
application uses the Any CPU platform target,
or the specific x86 or x64 targets. This is especially true
when working with unmanaged (native) code components, such
as DLLs. In these situations, you need to select the correct
platform target for your application.

As more and more systems start to be 64-bit, the easier
it is to overcome the infamous 2 GB memory limit. Although
.NET currently limits the size of single objects to around 2
GB, you can easily use multiple objects. This way, you can
break free from the limits that 32-bit systems have. In this
article, you learned the basics of moving your code from 32-
bit to 64-bit, and what this means for .NET developers. You
also saw how interoperability is affected, and how you could
at least approximately detect whether the system is a 32-bit
or a 64-bit one. With this information, you should be well
equipped to migrate to 64-bit .NET code.

Happy hacking!
Jani Jarvinen

About the Author

Jani Jarvinen

Jani Jarvinen is a software development trainer and consultant in Finland. He is a Microsoft C# MVP, a frequent author and has published three books about software development. He is the group leader of a Finnish software development expert group at ITpro.fi and a board member of the Finnish Visual Studio Team System User Group. His blog can be found at http://www.saunalahti.fi/janij/. You can send him mail by clicking on his name at the top of the article.

Advertiser Disclosure:
Some of the products that appear on this site are from companies from which QuinStreet receives compensation. This compensation may impact how and where products appear on this site including, for example, the order in which they appear. QuinStreet does not include all companies or all types of products available in the marketplace.

Thanks for your registration, follow us on our social networks to keep up-to-date