Introducing 64-bit Windows

[This is preliminary documentation and subject to change.]

A 64-bit operating system supports far more physical memory than a 32-bit operating system. For example, 32-bit Windows NT® supports 4 gigabytes of memory, while 64-bit Windows NT supports 16 terabytes of memory. The increased physical memory includes the following benefits for applications:

  1. Each application can support more users. All or part of each application must be replicated for each user, which requires additional memory.
  2. Each application has better performance. Increased physical memory allows more applications to run simultaneously and remain completely resident in the system's main memory. This reduces or eliminates the performance penalty of swapping pages to and from disk.
  3. Each application has more memory for data storage and manipulation. Databases can store more of their data in the physical memory of the system. Data access is faster because disk reads are not necessary.
  4. Applications can manipulate large amounts of data easily and more reliably. Video composition for motion picture work requires 64-bit Windows for this reason. Modeling for scientific and financial applications benefits greatly from memory-resident data structures that are not possible on 32-bit Windows.

There are also important benefits for businesses:

  1. Increased productivity. Knowledge workers can spend their time thinking and producing, rather than waiting for the software to finish its tasks.
  2. Lower cost of ownership. Each server can support larger numbers of users and applications, so your business will require fewer servers. This translates directly into less management overhead—one of the highest costs in any computing environment.
  3. New application opportunities. New applications can be designed without the barriers imposed by 32-bit Windows. New graphics applications will make work easier and more enjoyable. Data-intensive tasks that are impossible today can be done with 64-bit Windows.

For more information, see the following topics:

  • Getting Ready for 64-bit Windows
  • Designing 64-bit Compatible Interfaces
  • Running 32-bit Applications
  • Migration Tips

Microsoft Platform SDK, February 2001 Edition.
This content last built on Thursday, February 01, 2001.


Getting Ready for 64-bit Windows

The team creating the 64-bit version of Microsoft® Windows® wants to make it possible for developers to use a single source-code base for their 32- and 64-bit applications. The team has added support for this goal in the Platform SDK.

This overview describes how to make your source code support both 32- and 64-bit computing. Familiarity with existing data types will help.

  • Abstract Data Models
  • The New Data Types
  • The Environment
  • The Tools
  • Rules for Using Pointers
  • Virtual Address Space
  • Process Interoperability
  • Drivers
  • Comments and Feedback


Abstract Data Models

Every application and every operating system has an abstract data model. Many applications do not explicitly expose this data model, but the model guides the way in which the application's code is written. In the 32-bit programming model (known as the ILP32 model), integer, long, and pointer data types are 32 bits in length. Most developers have used this model without realizing it. For the history of the Win32® API, this has been a valid (although not necessarily safe) assumption to make.

In 64-bit Windows®, this assumption of parity in data type sizes is invalid. Making all data types 64 bits in length would waste space, because most applications do not need the increased size. However, applications do need pointers to 64-bit data, and they need the ability to have 64-bit data types in selected cases. These considerations led the team to select an abstract data model called LLP64 (or P64). In the LLP64 data model, only pointers expand to 64 bits; all other basic data types (integer and long) remain 32 bits in length.

Initially, the team expects most applications that run on 64-bit Windows to have been ported from 32-bit Windows. It is a major goal of the team that the same source, carefully written, should run on both 32- and 64-bit Windows. Defining the data model does not make this task easier. However, ensuring that the data model affects only pointer data types is the first step. The second step is to define a set of new data types that allow developers to automatically size their pointer-related data. This allows data associated with pointers to change size as the pointer size changes from 32 bits to 64 bits. Basic data types remain 32 bits in length, so there is no change in the size of data on the disk, data shared over a network, or data shared through memory-mapped files. This relieves developers of much of the effort involved in porting 32-bit code to 64-bit Windows.

These new data types have been added to the Platform SDK header files. Therefore, you can start using the new types now. For more information, see The New Data Types.


The New Data Types

There are three classes of new data types: fixed-precision data types, pointer-precision types, and specific-precision pointers. These types were added to the development environment (specifically, to Basetsd.h) to allow developers to prepare for 64-bit Windows® well before its introduction. These new types were derived from the basic C-language integer and long types, so they work in existing code. Therefore, use these data types in your code now, test your code on 32-bit Windows, and recompile with the 64-bit compiler when 64-bit Windows is available.

There is an additional benefit to adopting these new data types now: it will make your code more robust. To use these data types, you must scan your code for potentially unsafe pointer usage, polymorphism, and data definitions. To be safe, use the new types. For example, when a variable is of type ULONG_PTR, it is clear that it will be used for casting pointers for arithmetic operations or polymorphism. It is not possible to indicate such usage directly by using the existing data types. You can do this by using derived type naming or Hungarian notation, but both techniques are prone to errors.

Fixed Precision

Fixed-precision data types are the same length in both 32- and 64-bit Windows. To help you remember this, their precision is part of the name of the data type. The following are the fixed-precision data types.

Type | Definition
DWORD32 | 32-bit unsigned integer
DWORD64 | 64-bit unsigned integer
INT32 | 32-bit signed integer
INT64 | 64-bit signed integer
LONG32 | 32-bit signed integer
LONG64 | 64-bit signed integer
UINT32 | Unsigned INT32
UINT64 | Unsigned INT64
ULONG32 | Unsigned LONG32
ULONG64 | Unsigned LONG64

Pointer Precision

As the pointer precision changes (that is, as it becomes 32 bits on 32-bit Windows and 64 bits with 64-bit Windows), these data types reflect the precision accordingly. Therefore, it is safe to cast a pointer to one of these types when performing pointer arithmetic; if the pointer precision is 64 bits, the type is 64 bits. The count types also reflect the maximum size to which a pointer can refer. The following are the pointer-precision and count types.

Type | Definition
DWORD_PTR | Unsigned long type for pointer precision.
HALF_PTR | Half the size of a pointer. Use within a structure that contains a pointer and two small fields.
INT_PTR | Signed integral type for pointer precision.
LONG_PTR | Signed long type for pointer precision.
SIZE_T | The maximum number of bytes to which a pointer can refer. Use for a count that must span the full range of a pointer.
SSIZE_T | Signed SIZE_T.
UHALF_PTR | Unsigned HALF_PTR.
UINT_PTR | Unsigned INT_PTR.
ULONG_PTR | Unsigned LONG_PTR.

Specific Pointer-Precision Types

There are also new pointer types that explicitly size the pointer. Be cautious when using pointers in 64-bit code: If you declare the pointer using a 32-bit type, the operating system creates the pointer by truncating a 64-bit pointer. (All pointers are 64 bits on 64-bit Windows.)

Type | Definition
POINTER_32 | A 32-bit pointer. On 32-bit Windows, this is a native pointer. On 64-bit Windows, this is a truncated 64-bit pointer.
POINTER_64 | A 64-bit pointer. On 64-bit Windows, this is a native pointer. On 32-bit Windows, this is a sign-extended 32-bit pointer. Note that it is not safe to assume the state of the high pointer bit.


The Environment

Developers working on applications for 64-bit Windows® will find the development environment virtually identical to the development environment for 32-bit Windows. The existing APIs have been modified where necessary to allow them to reflect the precision of the platform on which they are running. The result is simplicity and a short learning curve for the developer—writing code for 64-bit Windows is just like writing code for 32-bit Windows.

The Platform SDK supports new data types that allow pointers and pointer-associated variables to reflect the precision of the platform. This means that developers can compile a single source base to run natively on either 32-bit Windows or 64-bit Windows. This strategy reduces the cost of developing applications that leverage 64-bit hardware, such as the forthcoming IA-64 chip from Intel. When 64-bit Windows becomes available, simply recompile your code and test it on 64-bit Windows.

You will have more time to make your applications 64-bit ready if you adopt the new data-type conventions as soon as possible. If you are making maintenance changes or Windows 2000-specific changes to your code, you should change the data definitions at the same time. Test the application on 32-bit Windows, run it through the 64-bit compiler (described in The Tools), and the application will be ready when 64-bit Windows is available.


The Tools

This topic describes the tools available for you to use in making your application 64-bit ready.

Include Files

The API elements are virtually identical between 32- and 64-bit Windows®. The Windows header files have been modified so that you can use them for both 32- and 64-bit code. The new 64-bit types and macros are defined in a new header file, Basetsd.h, which is in the set of header files included by Windows.h. Basetsd.h includes the new data-type definitions you'll use to make your application word-size independent.

New Data Types

The Platform SDK header files contain new data types. These types are primarily for type compatibility with the 32-bit data types. The new types provide exactly the same typing as the existing types, while at the same time providing support for the upcoming 64-bit Windows. For more information, see The New Data Types or the Basetsd.h header file.

Predefined Macros

The compiler defines the following macros to identify the platform.

Macro | Meaning
_WIN64 | A 64-bit platform.
_WIN32 | A 32-bit platform. This value is also defined by the 64-bit compiler for backward compatibility.
_WIN16 | A 16-bit platform

The following macros are specific to the architecture.

Macro | Meaning
_M_IA64 | A 64-bit Intel platform.
_M_IX86 | A 32-bit Intel platform.

Do not use these macros except with architecture-specific code, instead, use _WIN64, _WIN32, and _WIN16 whenever possible.

Helper Functions

The following inline functions (defined in Basetsd.h) can help you safely convert values from one type to another.

unsigned long HandleToUlong( const void *h )
long HandleToLong( const void *h )
void *LongToHandle( const long h )
unsigned long PtrToUlong( const void *p )
unsigned int PtrToUint( const void *p )
unsigned short PtrToUshort( const void *p )
long PtrToLong( const void *p )
int PtrToInt( const void *p )
short PtrToShort( const void *p )
void * IntToPtr( const int i )
void * UIntToPtr( const unsigned int ui )
void * LongToPtr( const long l )
void * ULongToPtr( const unsigned long ul )

Warning IntToPtr sign-extends the int value, UIntToPtr zero-extends the unsigned int value, LongToPtr sign-extends the long value, and ULongToPtr zero-extends the unsigned long value.

64-bit Compiler

The Platform SDK includes a prerelease version of a 64-bit compiler that you can use to identify pointer truncation, improper type casts, and other 64-bit-specific problems. Better tools will be available, but this tool will get you started.

After you install the compiler, you can run it on a project or set of code. The first time you run the compiler, it will generate many pointer truncation or type mismatch warnings, such as the following:

warning C4311: 'type cast' : pointer truncation from 'unsigned char *' to 'unsigned long '

Use these warnings as a guide to make your code more robust. It is good practice to eliminate all warnings, especially pointer-truncation warnings. For example, the following code can generate the C4311 warning:

buffer = (PUCHAR)srbControl;
(ULONG)buffer += srbControl->HeaderLength;

To correct the code, make the following changes:

buffer = (PUCHAR)srbControl;
(ULONG_PTR)buffer += srbControl->HeaderLength;

64-bit Compiler Switches and Warnings

Note that this compiler enables the LLP64 data model.

There is a warning option to assist porting to LLP64. The -Wp64 -W3 switch enables the following warnings:

  • C4305: Truncation warning. For example, "return": truncation from "unsigned int64" to "long."
  • C4311: Truncation warning. For example, "type cast": pointer truncation from "int*_ptr64" to "int."
  • C4312: Conversion to bigger-size warning. For example, "type cast": conversion from "int" to "int*_ptr64" of greater size.
  • C4318: Passing zero length. For example, passing constant zero as the length to the memset function.
  • C4319: Not operator. For example, "~": zero extending "unsigned long" to "unsigned _int64" of greater size.
  • C4313: Calling the printf family of functions with conflicting conversion type specifiers and arguments. For example, "printf": "%p" in format string conflicts with argument 2 of type "_int64." Another example is the call printf("%x", pointer_value); this causes a truncation of the upper 32 bits. The correct call is printf("%p", pointer_value).
  • C4244: Same as the existing warning C4242. For example, "return": conversion from "_int64" to "unsigned int," possible loss of data.

64-bit Linker and Libraries

To build your application, use the linker and libraries provided by the Platform SDK. However, not all of the libraries have a corresponding 64-bit version, so your code may not link.


Rules for Using Pointers

Porting your code to compile for both 32- and 64-bit Windows® is straightforward. You need only follow a few simple rules about casting pointers, and use the new data types in your code. The rules for pointer manipulation are as follows.

  1. Do not cast pointers to int, long, ULONG, or DWORD.

    If you must cast a pointer to test some bits, set or clear bits, or otherwise manipulate its contents, use the UINT_PTR or INT_PTR type. These types are integral types that scale to the size of a pointer for both 32- and 64-bit Windows (for example, ULONG for 32-bit Windows and _int64 for 64-bit Windows). For example, assume you are porting the following code:

    ImageBase = (PVOID)((ULONG)ImageBase | 1);

    As a part of the porting process, you would change the code as follows:

    ImageBase = (PVOID)((ULONG_PTR)ImageBase | 1);

    Use UINT_PTR and INT_PTR where appropriate (and if you are uncertain whether they are required, there is no harm in using them just in case). Do not cast your pointers to the types ULONG, LONG, INT, UINT, or DWORD.

    Note that HANDLE is defined as a void*, so typecasting a HANDLE value to a ULONG value to test, set, or clear the low-order 2 bits is an error on 64-bit Windows.

  2. Use the PtrToLong or PtrToUlong function to truncate pointers.

    If you must truncate a pointer to a 32-bit value, use the PtrToLong or PtrToUlong function (defined in Basetsd.h). These functions disable the pointer truncation warning for the duration of the call.

    Use these functions carefully. After you convert a pointer variable using one of these functions, never use it as a pointer again. These functions truncate the upper 32 bits of an address, which are usually needed to access the memory originally referenced by pointer. Using these functions without careful consideration will result in fragile code.

  3. Be careful using OUT parameters.

    For example, suppose you have a function defined as follows:

    void func( OUT PULONG *PointerToUlong );

    Do not call this function as follows:

    ULONG ul;
PULONG lp;
func((PULONG *)&ul);
lp = (PULONG)ul;

    Instead, use the following call:

    PULONG lp;
func(&lp);

    Typecasting &ul to PULONG* prevents a compiler error, but the function will write a 64-bit pointer value into the memory at &ul. This code works on 32-bit Windows, but will cause data corruption on 64-bit Windows—and it will be subtle, hard-to-find corruption. The bottom line: Do not play tricks with the C code—straightforward and simple is better.

  4. Be careful with polymorphic interfaces.

    Do not create functions that accept DWORD parameters for polymorphic data. If the data can be a pointer or an integral value, use the UINT_PTR or PVOID type.

    For example, do not create a function that accepts an array of exception parameters typed as DWORD values. The array should be an array of DWORD_PTR values. Therefore, the array elements can hold addresses or 32-bit integral values. (The general rule is that if the original type is DWORD and it needs to be pointer width, convert it to a DWORD_PTR value. That is why there are corresponding pointer-precision types.) If you have code that uses DWORD, ULONG, or other 32-bit types in a polymorphic way (that is, you really want the parameter or structure member to hold an address), use UINT_PTR in place of the current type.

  5. Use the new window class functions.

    If you have window or class private data that contains pointers, your code will need to use the following new functions:

    • GetClassLongPtr
    • GetWindowLongPtr
    • SetClassLongPtr
    • SetWindowLongPtr

    These functions can be used on both 32- and 64-bit Windows, but they are required on 64-bit Windows. Prepare for the transition by using these functions now.

    Additionally, you must access pointers or handles in class private data using the new functions on 64-bit Windows. To aid you in finding these cases, the following indexes are not defined in Winuser.h during a 64-bit compile:

    • GWL_WNDPROC
    • GWL_HINSTANCE
    • GWL_HWDPARENT
    • GWL_USERDATA

    Instead, Winuser.h defines the following new indexes:

    • GWLP_WNDPROC
    • GWLP_HINSTANCE
    • GWLP_HWNDPARENT
    • GWLP_USERDATA
    • GWLP_ID

    For example, the following code does not compile:

    SetWindowLong(hWnd, GWL_WNDPROC, (LONG)MyWndProc);

    It should be changed as follows:

    SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)MyWndProc);

    When setting the cbWndExtra member of the WNDCLASS structure, be sure to reserve enough space for pointers. For example, if you are currently reserving sizeof(DWORD) bytes for a pointer value, reserve sizeof(DWORD_PTR) bytes.

  6. Access all window and class data using FIELD_OFFSET.

    It is common to access window data using hard-coded offsets. This technique is not portable to 64-bit Windows. To make your code portable, access your window and class data using the FIELD_OFFSET macro. Do not assume that the second pointer has an offset of 4.

  7. The LPARAM, WPARAM, and LRESULT types change size with the platform.

    When compiling 64-bit code, these types expand to 64 bits, because they typically hold pointers or integral types. Do not mix these values with DWORD, ULONG, UINT, INT, int, or long values. Examine how you use these types and ensure that you do not inadvertently truncate values.


Virtual Address Space

By default, 64-bit Windows®-based applications have a user-mode address space of 8 terabytes. However, you can specify that the system should allocate all memory for the application below 2 gigabytes. This feature is beneficial for 64-bit applications if the following conditions are true:

  • A 2 GB address space is sufficient.
  • The code has many pointer truncation warnings.
  • Pointers and integers are freely mixed.
  • The code has polymorphism using 32-bit data types.

All pointers are still 64-bit pointers, but the system insures that every memory allocation occurs below the 2G, so that if the application truncates a pointer, no significant data is lost. Pointers can be truncated to 32-bit values, then extended to 64-bit values by either sign extension or zero extension.

To specify this memory limitation, use the /LARGEADDRESSAWARE:NO linker option. However, be aware that problems can occur when using this option. If you build a DLL that uses this option and the DLL is called by an application that does not use this option, the DLL could truncate a 64-bit pointer whose upper 32 bits are significant. This can cause application failure without any warning.


Process Interoperability

You can run Win32-based applications on 64-bit Windows® using an emulation layer. For more information, see Running 32-bit Applications.

On 64-bit Windows, a 64-bit process cannot load a 32-bit dynamic-link library (DLL). Additionally, a 32-bit process cannot load a 64-bit DLL. However, 64-bit Windows supports remote procedure calls (RPC) between 64-bit and 32-bit processes (both on the same computer and across computers). On 64-bit Windows, an out-of-process 32-bit COM server can communicate with a 64-bit client, and an out-of-process 64-bit COM server can communicate with a 32-bit client. Therefore, if you have a 32-bit DLL that is not COM-aware, you can wrap it in an out-of-process COM server and use COM to marshal calls to and from a 64-bit process.

In-process servers are currently registered using the InprocServer registry entry. On 64-bit Windows, 64- and 32-bit in-process servers should use the InprocServer32 entry.

To port handles, which by their nature are local to the computer and would never be used across the 32-bit to 64-bit boundary, use the HANDLE_PTR type instead of the INT_PTR or DWORD_PTR type. This includes porting RPC interfaces passing such handles as DWORD values. The 64-bit HANDLE_PTR is 64 bytes on the wire (not truncated) and thus does not need mapping.

For more information, see Designing 64-bit-Compatible Interfaces.


Drivers

When porting drivers to 64-bit Windows®, consider the following points:

  • If your device supports more than 4 GB, you can use the Mm64BitPhysicalAddresses value to determine if 64-bit addressing is needed. In addition, you can use the Dma64BitAddresses member of the DEVICE_DESCRIPTION structure to indicate that 64-bit addressing is supported.
  • The information field in the I/O status block is of type ULONG_PTR.
  • The parameters in the IRP stack locations are of type ULONG_PTR.
  • The only HalX xx functions your driver should use are the ones included in Wdm.h.


Comments and Feedback

Please send your comments and feedback to nt64feed@microsoft.com.


Designing 64-bit-Compatible Interfaces

Porting from 32-bit Windows to 64-bit Windows should not, by itself, create any problems for distributed applications, whether they use Remote Procedure Calls (RPC) directly or through DCOM. The RPC programming model specifies well-defined data sizes and integer types that are the same size on each end of the connection. Also, in the LLP64 abstract data model developed for 64-bit Windows, only the pointers expand to 64 bits—all other integer data types remain 32 bits. Because pointers are local to each side of the client/server connection and are usually transmitted as NULL or non-NULL markers, the marshaling engine can handle different pointer sizes on either end of a connection transparently.

However, backward compatibility issues arise when you add new data types or methods to an interface, change old data types, or use data types inappropriately. The following topics discuss how to avoid these situations (when possible) and how to design robust workarounds when it is not possible to avoid them.

  • Changing an Existing Interface
  • Avoiding Information Hiding
  • Avoiding Polymorphism
  • Using New Data Types in Your IDL File
  • Preparing Your Application for 64-bit Windows

If you are not already familiar with the new data types, working environment, and API changes for 64-bit Windows, see Getting Ready for 64-bit Windows.


Changing an Existing Interface

Whenever possible, implement a new interface for your application, rather than making changes to an existing one. If you cannot avoid changing an existing interface, use new data types in new methods only. Introducing a new data type or modifying an existing type is the most common source of incompatibility problems. The RPC run-time model assumes that the receiving application knows about the types of data that it receives, so data is put onto the wire without a generic data description. When the recipient expects a different data type than what the sender has put on the wire, the stub raises an exception (or transmission fails in some other less graceful manner).

An RPC interface is defined by its UUID and its major and minor version numbers. When you change an existing interface, you should add the new methods at the end of the interface and change the minor version number. If you add methods anywhere else or make any other changes to the interface, you will also need to change the major version number.

Realistically, there are times when you cannot change even the minor version number, because a new client will not be able to communicate with an old server and you cannot update the server. The RPC run time raises an exception, RPC_S_PROCNUM_OUT_OF_RANGE, when a client calls a method beyond the ones specified for its interface with the server. The workaround is to leave the version numbers unchanged and write your client code to handle this exception gracefully—by the client degrading its performance, for example, or by whatever means are appropriate for your application.

There is a similar workaround for one special case of changing a data type in an existing method. If you have a union whose branches are pointers and that does not have a default branch for unrecognized types, you can add a new branch that uses the new data type. This will not change the size of the data structure. When your client talks to a new server, it can use the new data type. However, when your client talks to an old server, the run time will raise the exception RPC_S_INVALID_TAG. Again, you will need to write your client code to handle this exception appropriately.

A DCOM interface is identified by its GUID. In DCOM, interfaces are considered immutable and you can make changes only by creating a new interface that inherits from the old one. These rules ensure that clients and servers remain compatible.


Avoiding Information Hiding

Occasionally, programs deliberately or inadvertently hide information from the RPC marshaling engine. Some examples are as follows:

  • Sending a data structure as an undifferentiated block of bytes
  • Leveraging performance by using a side effect from a method to channel additional data across the wire
  • Attempting to disguise a handle by passing it as a DWORD or a ULONG

These techniques are almost guaranteed to introduce compatibility problems even before you port your application to 64-bit Windows.

Instead of sending a server context as a DWORD in a standard remote procedure call, use a context handle to provide an opaque handle to a server context that is held on behalf of a client. Contexts are identified by GUIDs defined by the RPC run time when a server creates a context handle for a client. No pointer is used over the wire and the operation is completely transparent across 32- or 64-bit boundaries. For more information on using context handles, see Context Handles.

DCOM interfaces cannot use context handles because COM provides its own context management. Instead of creating a context handle, you can pass an interface pointer to the COM object. Then you can call the methods directly through the interface pointer or place the pointer inside other calls. To release the server object, the client calls the interface's Release method through the interface pointer.

Again, there may be times when you cannot change the original design of the code that you are porting. If there is no way to avoid sending a pointer across the wire as a DWORD, you will have to implement some form of server-side mapping between DWORDs and pointers. One way to do this is to change the pointers in the client side application to pointer-precision types, such as ULONG_PTR or DWORD_PTR. Then use the MIDL [call_as] attribute to put the pointers on the wire as DWORD values. The client-side wrapper need only pass the arguments along. The server-side wrapper handles the mapping between both types. In a similar way, you can use either the [transmit_as] attribute or the [represent_as] attribute to convert your data to a backward-compatible format for wire representation.

If backward-wire compatibility is not an issue or if the handle is not used for remote calls and you are sure that remote calls between 32- and 64-bit processes will never happen, you can redefine an argument as a ULONG64. If necessary, you can modify the 32-bit application to pass a DWORD to the user. Alternatively, you can build separate stubs from separate IDL files for each platform using a DWORD on 32-bit Windows and a ULONG64 on 64-bit Windows.


Avoiding Polymorphism

The new data types includes two polymorphic types, INT_PTR and LONG_PTR. On 32-bit Windows, the INT_PTR maps to int and the LONG_PTR maps to long. On 64-bit Windows, both types map to the __int64 intrinsic type. The MIDL compiler supports these types for remote procedure calls, but there is an inherent limitation that you must keep in mind when using them in a distributed environment. Be sure to comment your code accordingly.

Regardless of the platform size, the wire size of these polymorphic types is always 32 bits. When unmarshaling on 64-bit Windows, the run-time library sign extends signed values and assigns zero to the high-order bytes for an unsigned value. When putting a 64-bit value on the wire, the run time truncates the high-order bytes. Thus, only the low-order 32-bit values are usable.

Use the polymorphic types only when necessary for porting. For new interfaces, use the MIDL intrinsic integer types __int32 and __int64, or use a pointer type or context handle, whichever is most appropriate for the kind of data being transferred.

The 64-bit compiler supports a new polymorphic intrinsic __int3264. Again, this type was developed to support porting efforts, in this case to support the UINT_PTR types transparently. (Another intrinsic, __long3264, will support the ULONG_PTR type.) Do not use __int3264 directly; use the INT_PTR type when you need a polymorphic type for porting reasons.


Using New Data Types in Your IDL File

The Basetsd.h header file defines the new data types needed for writing applications that run on both 32- and 64-bit Windows. To use these data types in your interfaces, import Basetsd.h into your IDL file. Do not #include the file or you will end up with multiple definitions at compile time.

Alternatively, you can include or import the Basetsd.idl file into your IDL file.

For more information on adding system header files to your IDL file, see Importing Files and Type Libraries and Importing System Header Files.


Preparing Your Application for 64-bit Windows

The Microsoft Platform Software Development Kit (SDK) and Windows 2000 include several features that make it easier for you to develop applications that will run on both 32- and 64-bit Windows. Most of these, such as the new data types, are described in Getting Ready for 64-bit Windows .

The 64-bit toolkit that ships with the Platform SDK includes a 64-bit MIDL compiler, Midl.exe, for generating native 64-bit stubs, as well as 32-bit stubs. Use the /env win64 switch to generate 64-bit stubs only. The default is to generate dual stubs that run on both platforms.

Note that the 64-bit MIDL supports the /Oicf and /Os optimization modes only.


Running 32-bit Applications

WOW64 is the x86 emulator that allows Win32-based applications to run on 64-bit Windows®. It is intended to run 32-bit personal productivity applications needed by software developers and administrators. It is not intended to run 32-bit server applications.

WOW64 launches and runs 32-bit applications seamlessly. The system isolates 32-bit applications from 64-bit applications, including preventing file and registry collisions. Both console and GUI applications are supported, as are service applications. The system provides interoperability across the 32/64 boundary for scenarios such as cut and paste and COM. However, 32-bit processes cannot load 64-bit DLLs, and 64-bit processes cannot load 32-bit DLLs.

For more information on the features and limitations of WOW64, see the following topics:

  • Performance and Memory Consumption
  • WOW64 Implementation Details
  • Reasons for 64/32 Restrictions
  • Registry Redirector
  • File System Redirector
  • Memory Management
  • Processor Affinity
  • Interprocess Communication
  • Application Installation
  • Debugging WOW64

Note that 64-bit Windows does not support running 16-bit Windows-based applications. The primary reason is that handles have 32 significant bits on 64-bit Windows. Therefore, handles cannot be truncated and passed to 16-bit applications without loss of data. Attempts to launch 16-bit applications will fail with ERROR_BAD_EXE_FORMAT.


Performance and Memory Consumption

Performance and memory consumption are determined by the following factors:

  • Processor hardware (instruction emulation is performed on the chip).
  • API thunk overhead. This overhead is small compared to the NT kernel call itself. NT kernel functions are intended to be called infrequently.
  • Virtual memory size. WOW64 adds significant overhead if two or more instances of the same 32-bit application are running concurrently. This adversely affects the scalability of Terminal Services.
  • Working set. WOW64 increases the size of the application's working set.

WOW64 enables 32-bit applications to take advantage of the 64-bit kernel. Therefore, 32-bit applications can use a larger number of kernel handles and window handles. However, 32-bit applications cannot create as many threads under WOW64 as they can on x86, because there is less virtual address space available, and each thread contains a 64-bit stack (usually 512K).


WOW64 Implementation Details

The WOW64 emulator runs in user mode, sitting between the x86 version of Ntdll.dll and the IA-64 kernel, intercepting kernel calls. The emulator consists of the following DLLs:

  • Wow64.dll. This DLL provides the core emulation infrastructure and the thunks for the Ntoskrnl.exe entry-point functions.
  • Wow64win.dll. This DLL provides thunks for the Win32k.sys entry-point functions.
  • Wow64cpu.dll. This DLL provides x86 instruction emulation. It executes mode-switch instructions on IA-64.

Along with the 64-bit version of Ntdll.dll, these are the only 64-bit binaries that can be loaded into a 32-bit process.

At startup, Wow64.dll loads the x86 version of Ntdll.dll and runs its initialization code, which loads all necessary 32-bit DLLs. Nearly all 32-bit DLLs are unmodified copies of 32-bit Whistler binaries. However, some of these DLLs have knowledge of WOW64, usually because they share memory with 64-bit processes.

Instead of using the x86 system-service call sequence, 32-bit binaries making system calls are rebuilt to use a custom calling sequence. This new sequence is inexpensive for WOW64 to intercept because it remains entirely in user mode. When the new calling sequence is detected, the WOW64 CPU transitions back to native 64-bit mode and calls into Wow64.dll. Thunking is done in user mode to reduce the impact on the 64-bit kernel, and to reduce the risk of a bug in the thunk causing a kernel-mode crash, data corruption, or a security hole. The thunks extract arguments from the 32-bit stack, extend them to 64 bits, then make the native system call.


Reasons for 64/32 Restrictions

There are several reasons that 64-bit processes cannot load 32-bit DLLs and 32-bit processes cannot load 64-bit DLLs.

  1. The thunk layer would have to copy all parameters and data they point to into the low 2G of the 64-bit application, because the 32-bit DLL cannot address memory above 2G. This is impossible if the 64-bit application passes a pointer to data larger than 2G. Even with smaller data structures, the Win32® execution model presents many barriers to reliable thunking, because object lifetimes are often not clearly defined, objects are passed from thread to thread or referenced asynchronously, or objects are highly polymorphic.
  2. A 32-bit DLL expects 4K pages and x86-style exception handling. On IA-64, the native page size is 8K and the WOW64 emulator simulates 4K pages with assistance from the kernel memory manager and the IA-64 CPU. WOW64 implements x86 exception handling as "pure"—no unwinds from x86 code to IA-64 and back to x86—because exceptions on an x86 machine do not unwind from user mode to kernel mode and back to user mode.
  3. System DLLs such as Kernel32.dll, User32.dll, and Gdi32.dll expect only one instance per process, either 32-bit or 64-bit. For example, User32.dll stores a pointer to an array of function pointers into the PEB at startup, and those functions are called through user mode APCs initiated by Win32k.sys. If a process contained two copies of User32.dll, Win32k.sys would not be able to decide which one to call. The WOW64 emulator stores its own table of function pointers in the PEB64, which thunk the parameters then make calls through the 32-bit function pointer table in the PEB32, so Win32k.sys does not have to disambiguate whether the window is 32-bit or 64-bit.


Registry Redirector

To support the coexistence of 32- and 64-bit COM registration and application states, WOW64 presents 32-bit applications with an alternate view of the registry. The 32-bit applications see an HKEY_LOCAL_MACHINE\Software registry tree that is completely separate from the true HKEY_LOCAL_MACHINE\Software tree. This isolates HKEY_CLASSES_ROOT, because the per-machine portion of this tree resides within the HKEY_LOCAL_MACHINE\Software tree.

To enable application interoperability through COM and other mechanisms, WOW64 uses a registry reflector, which copies certain registry keys and values between the two registry views. The reflector is intelligent, copying COM activation data for LocalServers between the views, but not Inproc data, because 32/64 inproc mixing is not permitted on 64-bit Windows®. The reflector uses a "last writer wins" policy for reflection, to support the following scenario:

  • After a clean install of 64-bit Windows, 64-bit Wordpad.exe is registered to handle .doc files. The reflector copies the .doc registration from the 64-bit registry view into the 32-bit registry view.
  • An administrator installs 32-bit Office, which registers Winword.exe to handler .doc files, in the 32-bit registry view. The registry reflector copies this information into the 64-bit registry view, so both 32- and 64-bit applications launch the 32-bit version of Winword.exe for .doc files.
  • An administrator installs 64-bit Office, which registers its Winword.exe to handle .doc files, in the 64-bit registry view. The registry reflector copies this information into the 32-bit registry, so both 32- and 64-bit applications launch the 64-bit version of Winword.exe for .doc files.

Therefore, the file association information is that of the most recently installed application.

Note that registry redirection is provided only for the registry on the computer on which the application is running. Registry redirection is not provided for remote registry access through the RegConnectRegistry function.


File System Redirector

The %systemroot%\System32 directory is reserved for 64-bit applications. Most DLL file names were not changed when porting to 64-bit, so 32-bit applications must use a different directory as their System32 directory. WOW64 hides this difference using a file system redirector. Whenever a 32-bit application attempts to access %systemroot%\System32, the access is redirected to a new directory, %systemroot%\SysWOW64. Certain subdirectories are exempt: for example, the Spool subdirectory is exempt from redirection to better support the print spooler. To retrieve the name of the 32-bit system directory, 64-bit applications should use the GetSystemWow64Directory function.

WOW64 does not redirect directories like %ProgramFiles%. Applications should use the SHGetSpecialFolderPath function to determine this directory name. The 32-bit registry view indicates the directory name is X:\Program Files (x86), where X is the directory containing the 64-bit Program Files directory.

To help applications that write REG_EXPAND_SZ keys containing %ProgramFiles% to the registry, WOW64 intercepts these writes and replaces them with "%ProgramFilesx86%". This environment variable is defined for all processes and points to X:\Program Files (x86).

The Session Manager creates two lists of DLLs on 64-bit Windows®: KnownDLLs lists 64-bit DLLs and KnownDLLs32 lists 32-bit DLLs. WOW64 intercepts references to the named object called KnownDLLs and redirects to KnownDLLs32.


Memory Management

WOW64 simulates 4K pages on top of the native IA-64 8K pages. The IA-64 processor assists, providing excellent simulation with low overhead. The simulation code cannot handle the following cases:

  • Write tracking. The GetWriteWatch and ResetWriteWatch functions are implemented in the kernel using native page-size granularity, so WOW64's 4K page simulation cannot determine which simulated 4K pages were written within the underlying 8K page.
  • Address Windowing Extensions. The AWE functions operate on page numbers, and there is no way to map 64-bit page numbers to 32-bit page numbers.
  • Section alignment. For executable images with section alignment smaller than 8K (the default is 4K for x86 images), WOW64 must dirty all image pages. This effectively copies each page to the pagefile, and prevents read-only image pages from being shared between processes.
  • Large addresses (through the /LARGEADDRESSAWARE:YES linker option). WOW64 restricts 32-bit processes to a 2G address space.
  • The ReadFileScatter and WriteFileGather functions.


Processor Affinity

While 64-bit Windows® supports a maximum of 64 processors, 32-bit Windows supports a maximum of 32 processors. Therefore, functions such as GetProcessAffinityMask simulate a computer with 32 processors when called under WOW64. The affinity mask is obtained by performing a bitwise OR operation of the top 32 bits of the mask with the low 32 bits. Therefore, if a thread has affinity for processors 0, 1, and 32, WOW64 reports the affinity as 0 and 1, because processor 32 maps to processor 0. Functions that set processor affinity, such as SetThreadAffinityMask, restrict processors to the first 32 processors under WOW64.


Interprocess Communication

The following techniques can be used for communication between 32- and 64-bit applications:

  • Handles to named objects such as mutexes, semaphores, and file handles can all be shared.
  • Handles to windows (HWND) can be shared.
  • RPC.
  • COM LocalServers.
  • Shared memory can be used if the contents of the shared memory are not pointer-dependent.
  • The CreateProcess and ShellExecute functions can launch 32-bit and 64-bit processes from either 32-bit or 64-bit processes.
  • The CreateRemoteThread function is special-cased for specific functions, allowing 64-bit debuggers to break into 32-bit processes.


Application Installation

Lack of support for 16-bit DOS, Windows®, or OS/2 applications on 64-bit Windows presents a significant barrier to application installation programs, because they often use a 16-bit stub to check the machine type, then launch a 32-bit install engine to actually perform the install.

To work around this problem, 64-bit Windows recognizes specific 16-bit installer programs and substitutes a ported 32-bit version. The list of substitutions is stored in the registry, under HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\NtVdm64. There is built-in support for ACME and InstallShield 5.x installers.

Note that the 64-bit Windows Installer can seamlessly install 32-bit MSI-based applications on 64-bit Windows.

For applications that must consist of 32- and 64-bit code, the installer must be 64-bit code. The issue is that a 32-bit installer cannot access the 64-bit system directory, or bypass the registry redirection. There are two possible solutions:

  • Create the primary installer as 32-bit code, but have it launch a 64-bit worker process to perform file copies and registration operations for the 64-bit code.
  • Create the primary installer as 64-bit code, but have it launch a 32-bit worker process to perform file copies and registration operations for the 32-bit code. A 64-bit installer can use the GetSystemWow64Directory function to retrieve the SysWow64 directory, and the KEY_WOW64_32KEY value with the registry functions to access the 32-bit view of the registry. In addition, the installer can use the %ProgramFile(x86) environment variable to access the 32-bit Program Files directory.


Debugging WOW64

Applications running under WOW64 can be debugged two ways:

  • Use an x86-hosted debugger, like NTSD, WinDBG, or Visual Studio®. The 32-bit NTSD is installed to %systemroot%\syswow64 on retail installs. Note that x86 debuggers can be used to debug x86 code, but cannot disassemble or set breakpoints within the WOW64 thunk layer because it is 64-bit native code.
  • Use a native debugger, like NTSD or WinDBG, and the WOW64 debugger extensions. The extensions (Wow64exts.dll and W64cpuex.dll) are installed with the operating system. If the native debugger breaks while the processor is in x86 mode, the debugger presents the process as an x86 process. If the processor is in native mode, the debugger presents the process as native. The !wow64exts.sw command switches the debugger between the two modes.


Migration Tips

The two primary areas of concern when examining your code for 64-bit compatibility are as follows:

  • Address calculations
  • Pointer arithmetic

For many reasons, developers have stored addresses as a ULONG value. After all, on 32-bit Windows, an address, a pointer, and a ULONG value are all 32 bits long. However, on 64-bit Windows, an address and a ULONG are not the same length. While a ULONG remains a 32-bit value, all pointers are now 64-bit values.

For more information, see the following topics:

  • General Porting Guidelines
  • Storing a 64-bit Value
  • Common Compiler Errors
  • Additional Considerations


General Porting Guidelines

Porting 32-bit applications to 64-bit Windows® will be easier than it was porting 16-bit applications to 32-bit Windows. However, the move will go more smoothly with some careful planning. The following are some general guidelines.

Planning

  • Determine the magnitude of the effort required for the port. Gauge how much work is involved by identifying the following items:
    • Identify problem 32-bit code. Compile your 32-bit code with the 64-bit compiler and examine the extent of the errors and warnings.
    • Identify shared components or dependencies. Determine which components in your application originate from other teams and whether those teams plan to develop 64-bit versions of their code.
    • Identify legacy or assembly code. 16-bit Windows-based applications do not run on 64-bit Windows and must be rewritten. While x86 assembly code runs in WOW64, you may want to rewrite this code to take advantage of the speed of the IA-64 architecture.
  • Port the entire application, not just portions of it. Although it is possible to port pieces of an application or to limit code to 2G with /LARGEADDRESSAWARE:NO, this strategy trades short-term gain for long-term pain.
  • Find substitutes for technologies that will not be ported. Some technologies, including DAO (Data Access Object) and the Jet Red database engine, will not be ported to 64-bit Windows.
  • Treat your 64-bit version as a separate product release. Even though your 64-bit product may share the same code base as your 32-bit, it needs additional testing and may have other release considerations.

Development

  • Start developing compliant code now. Developers can start writing compliant code by using the latest Platform SDK header files and the new data types with no adverse effects on 32-bit product development. For more information, see Getting Ready for 64-bit Windows.
  • Ensure that your code can be compiled for both 32- and 64-bit Windows. The new data model was designed to allow 32- and 64-bit applications to be built from a single code base with few modifications. The SQL Server and Windows 2000 development teams are developing 32- and 64-bit versions of their products from the same code base.
  • Use the compiler's new optimization features for best performance. Code optimization for IA-64 processors is more important than it was for the x86. The compiler assumes many of the optimization functions previously handled by the microprocessor. You can maximize the performance of a 64-bit application by using two new optimization features of the compiler: Profile Guided Optimization and Whole Program Optimization. Both features result in longer build times and require the early development of good test scenarios.
    • Profile Guided Optimization involves a two-step compile process. During the first compile, the code is instrumented to capture the execution behavior. This information is used during the second compile to guide all optimization features.
    • Whole Program Optimization analyzes the code in all application files, not just a single one. This approach increases performance in several ways, including better inlining, as well as improved side-effect analysis and custom calling conventions.

Testing

  • Determine whether you'll test 64- or 32-bit code running in WOW64. Some applications include both native 64-bit code and 32-bit code running in WOW64. Investigate this closely while developing a test plan, and decide whether your test tools should be 64-bit, 32-bit, or a combination. You will often need to test both the 64- and 32-bit versions of your application on 64-bit Windows.
  • Test frequently used 32-bit components. First, recompile your code to 64-bit and test. Second, fix problems, recompile in 32-bits, and then test. Third, recompile to 64-bit and test.
  • Test COM and RPC components. Make sure that both 32- and 64-bit COM and RPC components communicate correctly. You may also have to test communications with 16-bit components over a network.
  • Test your 32-bit version on 64-bit Windows. Customers can continue to use 32-bit applications on 64-bit Windows where performance and memory issues are not major considerations.
  • Test different memory configurations. Adding large amounts of memory on the server sometimes exposes previously unnoticed problems in either the application or the operating system.


Storing a 64-bit Value

To store a 64-bit pointer value, use ULONG_PTR. A ULONG_PTR value is 32 bits when compiled with a 32-bit compiler and 64 bits when compiled with a 64-bit compiler.

The following examples use real-world code that has been ported to 64-bit Windows. Commentary on the steps to make the code 64-bit compatible is included.

Example 1: Getting an Address

The following code illustrates a portable way to get an address.

Using ULONG (a 32-bit-only method)

ULONG getAnAddress( )
Int *somePointer
Return( (ULONG) somePointer );

Using ULONG_PTR (the portable method)

ULONG_PTR getAnAddress( )
Int *somePointer
Return( (ULONG_PTR) somePointer );

Example 2: Calculating an Address

The following code illustrates a portable way to calculate an address.

Using ULONG (a 32-bit-only method)

Int *somePointer;
Int *someOtherPointer;
somePointer = (int *)( (ULONG)someOtherPointer + 0x20 );

Using ULONG_PTR (the portable method)

Int *somePointer;
Int *someOtherPointer;
somePointer = (int *)( (ULONG_PTR)someOtherPointer + 0x20 );


Common Compiler Errors

This section illustrates the typical compiler errors that occur when migrating an existing code base. These examples happen to be from system-level HAL code, although the concepts are directly applicable to user-level code.

Warning C4311

'type cast' : pointer truncation from 'void *__ptr64 ' to 'unsigned long

Code

pPciAddr->u.AsULONG = (ULONG) CIA_PCI_CONFIG_BASE_QVA;

Problem Description

PtrToUlong is an inline function or macro, depending on your usage. It truncates a pointer to a ULONG. Although 32-bit pointers are not affected, the upper half of a 64-bit pointer is truncated.

CIA_PCI_CONFIG_BASE_QVA is declared as a PVOID. The ULONG cast works in the 32-bit world, but results in an error in the 64-bit world. The solution is to get a 64-bit pointer to a ULONG, because changing the definition of the union that pPciAddr->u.AsULONG is defined in changes too much code.

Using the macro PtrToUlong to convert the 64-bit PVOID to the needed ULONG is allowed because we have knowledge about the specific value of CIA_PCI_CONFIG_BASE_QVA. In this case, this pointer will never have data in the upper 32 bits.

The Fix

pPciAddr->u.AsULONG = PtrToUlong(CIA_PCI_CONFIG_BASE_QVA);

Warning C4311

'type cast' : pointer truncation from 'struct _ERROR_FRAME *__ptr64 ' to 'unsigned long

Code

KeBugCheckEx( DATA_BUS_ERROR,0,0,0,(ULONG)PUncorrectableError );

Problem Description

The problem is that the last parameter to this function is a pointer to a data structure. Because PUncorrectableError is a pointer, it changes size with the programming model. The prototype for KeBugCheckEx was changed so that the last parameter is a ULONG_PTR. As a result, it's necessary to cast the function pointer to a ULONG_PTR.

You might ask why PVOID was not used as the last parameter. Depending on the context of the call, the last parameter may be something other than a pointer or perhaps an error code.

The Fix

KeBugCheckEx( DATA_BUS_ERROR,0,0,0,(ULONG_PTR)PUncorrectableError );

Warning C4244

'=' : conversion from 'struct _CONFIGURATION_COMPONENT *__ptr64 ' to 'struct _CONFIGURATION_COMPONENT *', possible loss of data

Code

Component = &CurrentEntry->ComponentEntry;

Problem Description

The function declares the variable Component as a PCONFIGURATION_COMPONENT. Later, the variable is used in the following assignment that appears correct:

Component = &CurrentEntry->ComponentEntry;

However, the type PCONFIGURATION_COMPONENT is defined as:

typedef struct __CONFIGURATION_COMPONENT {
...
...
} CONFIGURATION_COMPONENT, * POINTER_32 PCONFIGURATION_COMPONENT;

The type definition for PCONFIGURATION_COMPONENT provides a 32-bit pointer in both 32-bit and 64-bit models because it is declared POINTER_32. The original designer of this structure knew it was going to be used in a 32-bit context in the BIOS and expressly defined it for that use. This code works fine in 32-bit Windows because the pointers happen to be 32-bit. In 64-bit Windows, it does not work because the code is in 64-bit context.

The Fix

To work around this problem, use CONFIGURATION_COMPONENT * rather than the 32-bit PCONFIGURATION_COMPONENT . It is important to clearly understand the purpose of the code. If this code is intended to touch 32-bit BIOS or System space, this fix will not work.

POINTER_32 is defined in Ntdef.h and Winnt.h.

#ifdef (__AXP64__)
#define POINTER_32 _ptr32
#else
#define POINTER_32
#endif

Warning C4242

'=' : conversion from '__int64 ' to 'unsigned long ', possible loss of data

Code

ARC_STATUS HalpCopyNVRamBuffer (
IN PCHAR NvDestPtr,
IN PCHAR NvSrcPtr,
IN ULONG Length )
{

ULONG PageSelect1, ByteSelect1;

ByteSelect1 = (NvDestPtr - (PUCHAR)HalpCMOSRamBase) & CONFIG_RAM_BYTE_MASK;

ByteSelect1 = (NvDestPtr - (PUCHAR)HalpCMOSRamBase) & CONFIG_RAM_BYTE_MASK;

Description

This warning is generated because the calculation is using 64-bit values, in this case pointers, and placing the result in a 32-bit ULONG.

The Fix

Type cast the result of the calculation to a ULONG as shown here:

ByteSelect1 = (ULONG)(NvDestPtr - (PUCHAR)HalpCMOSRamBase) & CONFIG_RAM_BYTE_MASK;

Typecasting the result lets the compiler know you are sure about the result. That being said, make certain you understand the calculation and really are sure it will fit in a 32-bit ULONG.

If the result may not fit in a 32-bit ULONG, change the base type of the variable that will hold the result.

Warning C4311

'type cast' : pointer truncation from 'void *__ptr64 ' to 'unsigned long '

Code

ULONG HalpMapDebugPort(
IN ULONG ComPort,
OUT PULONG ReadQva,
OUT PULONG WriteQva)
{
ULONG ComPortAddress;

ULONG PortQva;

// Compute the port address, based on the desired com port.

switch( ComPort ){
case 1:
   ComPortAddress = COM1_ISA_PORT_ADDRESS;
   break;

case 2:
default:
   ComPortAddress = COM2_ISA_PORT_ADDRESS;
}
PortQva = (ULONG)HAL_MAKE_QVA(CIA_PCI_SPARSE_IO_PHYSICAL) + ComPortAddress;

// Return the QVAs for read and write access.

*ReadQva = PortQva;
*WriteQva = PortQva;

return ComPortAddress;
}

Problem Description

This entire function deals with addresses as integers, necessitating the need to type those integers in a portable way. All the local variables, intermediate values in calculations, and return values should be portable types.

The Fix

ULONG_PTR HalpMapDebugPort(
IN ULONG ComPort,
OUT PULONG_PTR ReadQva,
OUT PULONG_PTR WriteQva)
{
ULONG_PTR ComPortAddress;

ULONG_PTR PortQva;

// Compute the port address, based on the desired com port.

switch( ComPort ){
case 1:
   ComPortAddress = COM1_ISA_PORT_ADDRESS;
   break;

case 2:
default:
   ComPortAddress = COM2_ISA_PORT_ADDRESS;
}

PortQva = (ULONG_PTR)HAL_MAKE_QVA(CIA_PCI_SPARSE_IO_PHYSICAL) + ComPortAddress;

// Return the QVAs for read and write access.

*ReadQva = PortQva;
*WriteQva = PortQva;

return ComPortAddress;
}

This is the first time we have seen the new type PULONG_PTR, which is a pointer to a ULONG_PTR.

PULONG_PTR is a pointer that is itself 32 bits for 32-bit Windows and 64 bits for 64-bit Windows. It points to an unsigned integer, ULONG_PTR, that is 32 bits for 32-bit Windows and 64 bits for 64-bit Windows.

Warning C4311

'type cast' : pointer truncation from 'void *__ptr64 ' to 'unsigned long '

Code

BOOLEAN
HalpMapIoSpace (
VOID
)
{
PVOID PciIoSpaceBase;
PciIoSpaceBase = HAL_MAKE_QVA( CIA_PCI_SPARSE_IO_PHYSICAL );

//Map base addresses in QVA space.

HalpCMOSRamBase = (PVOID)((ULONG)PciIoSpaceBase + CMOS_ISA_PORT_ADDRESS);

Problem Description

Even though all QVA (Quasi Virtual Address) values are really 32-bit values at this stage and will fit in a ULONG, it is more consistent to treat all addresses as ULONG_PTR values when possible.

The pointer PciIoSpaceBase holds the QVA that is created in the macro HAL_MAKE_QVA. This macro returns a 64-bit value with the top 32 bits set to zero so the math will work. We could simply leave the code to truncate the pointer into a ULONG, but this practice is discouraged to enhance code maintainability and portability. For example, the contents of a QVA might change in the future to use some of the upper bits at this level, breaking the code.

The Fix

Be safe and use ULONG_PTR for all address and pointer math.

HalpCMOSRamBase = (PVOID)((ULONG_PTR)PciIoSpaceBase + CMOS_ISA_PORT_ADDRESS);

Warning C4311

'type cast' : pointer truncation from 'void *__ptr64 ' to 'unsigned long '

Code

PVOID
HalDereferenceQva(
PVOID Qva,
INTERFACE_TYPE InterfaceType,
ULONG BusNumber)

if ( ((ULONG) Qva & QVA_SELECTORS) == QVA_ENABLE ) {

return( (PVOID)( (ULONG)Qva << IO_BIT_SHIFT ) );
}
else {
return (Qva);
}

Problem Description

The compiler warns about the address of (&) and left shift (<<) operators if they are applied to pointer types. In the above code, Qva is a PVOID value. We need to cast that to an integer type to perform the math. Because the code must be portable, use ULONG_PTR instead of ULONG.

The Fix

if ( ((ULONG_PTR) Qva & QVA_SELECTORS) == QVA_ENABLE ) {
  return( (PVOID)( (ULONG_PTR)Qva << IO_BIT_SHIFT ) );

Warning C4311

'type cast' : pointer truncation from 'void *__ptr64 ' to 'unsigned long '

Code

TranslatedAddress->LowPart = (ULONG)HalCreateQva(
*TranslatedAddress, va);

Description

TranslatedAddress is a union that looks something like the following:

typedef union
   Struct {
      ULONG LowPart;
      LONG Highpart;
   }
   LONGLONG QuadPart;
}

The Fix

Knowing what the rest of the code might place in Highpart , we can select either of the solutions shown here.

TranslatedAddress->LowPart = PtrToUlong(HalCreateQva(*TranslatedAddress,va) );

The PtrToLong macro truncates the pointer returned by HalCreateQva to 32 bits. We know that the QVA returned by HalCreateQva has the upper 32 bits set to zero and the very next line of code sets TranslatedAddress->Highpart to zero.

With caution, we could use the following:

TranslatedAddress->QuadPart = (LONGLONG)HalCreateQva(*TranslatedAddress,va);

This works in this example: the HalCreateQva macro is returning 64 bits, with the upper 32 bits set to zero. Just be careful not to leave the upper 32 bits undefined in a 32-bit environment, which this second solution may actually do.

Warning C4311

'type cast' : pointer truncation from 'void *__ptr64 ' to 'unsigned long '

Code

VOID
HalpCiaProgramDmaWindow(
PWINDOW_CONTROL_REGISTERS WindowRegisters,
PVOID MapRegisterBase
)
{
CIA_WBASE Wbase;

Wbase.all = 0;
Wbase.Wen = 1;
Wbase.SgEn = 1;
Wbase.Wbase = (ULONG)(WindowRegisters->WindowBase) >> 20;

Problem Description

WindowRegisters->WindowBase is a pointer and is now 64 bits. The code says to right-shift this value 20 bits. The compiler will not let us use the right-shift (>>) operator on a pointer; therefore, we need to cast it to some sort of integer.

The Fix

Wbase.Wbase= PtrToUlong ( (PVOID) ((ULONG_PTR) (WindowRegisters->WindowBase) >> 20));

Casting to a ULONG_PTR is just what we need. The next problem is Wbase. Wbase is a ULONG and is 32 bits. In this case, we know that the 64-bit pointer WindowRegisters->WindowBase is valid in the lower 32 bits even after being shifted. This makes use of the PtrToUlong macro acceptable, because it will truncate the 64-bit pointer into a 32-bit ULONG. The PVOID cast is necessary because PtrToUlong expects a pointer argument. When you look at the resulting assembler code, all this C code casting becomes just a load quad, shift right, and store long.


Additional Considerations

When porting your code, consider the following points:

  • The following assumption is no longer valid:
    #ifdef _WIN32 // Win32 code
...
#else         // Win16 code
...
#endif

    However, the 64-bit compiler defines _WIN32 for backward compatibility.

  • The following assumption is no longer valid:
    #ifdef _WIN16 // Win16 code
...
#else         // Win32 code
...
#endif

    In this case, the else clause can represent _WIN32 or _WIN64.

  • Be careful with data-type alignment. The TYPE_ALIGNMENT macro returns the alignment requirements of a data type. For example:
    TYPE_ALIGNMENT( KFLOATING_SAVE ) == 4 on x86, 8 on IA-64
TYPE_ALIGNMENT( UCHAR ) == 1 everywhere

    As an example, kernel code that currently looks like this:

    ProbeForRead( UserBuffer, UserBufferLength, sizeof(ULONG) );

    should probably be changed to:

    ProbeForRead( UserBuffer, UserBufferLength, TYPE_ALIGNMENT(IOCTL_STRUC) );

    The current plan is to not turn on automatic fixes of kernel-mode alignment exceptions for 64-bit systems, as they are currently for 32-bit systems.

  • Be careful with NOT operations. Consider the following:
    UINT_PTR a; ULONG b;
a = a & ~(b - 1);

    The problem is that ~(b–1) produces 0x0000 0000 xxxx xxxx and not 0xFFFF FFFF xxxx xxxx. The compiler will not detect this. To fix this, change the code as follows:

    a = a & ~((UINT_PTR)b - 1);
  • Be careful performing unsigned and signed operations. Consider the following:
    LONG a;
ULONG b;
LONG c;

a = -10
b = 2;
c = a / b;

    The result is unexpectedly large. The rule is that if either operand is unsigned, the result is unsigned. In the preceding example, a is converted to a signed value and stored in c. Both conversions involve no numeric manipulation.

    As another example, consider the following:

    ULONG x;
LONG y;
LONG *pVar1;
LONG *pVar2;

pVar2 = pVar1 + y * (x - 1);

    The problem arises because x is unsigned, which makes the entire expression unsigned. This works fine unless y is negative. In this case, y is converted to an unsigned value, the expression is evaluated using 32-bit precision, scaled, and added to pVar1. A 32-bit unsigned negative number becomes a large 64-bit positive number, which gives the wrong result. To fix this problem, declare x as a signed value or explicitly typecast it to LONG in the expression.

  • Be careful when making piecemeal size allocations. For example:
    struct xx {
   DWORD NumberOfPointers;
   PVOID Pointers[1];
};

    The following code is wrong because the compiler will pad the structure with an additional 4 bytes to make the 8-byte alignment:

    malloc(sizeof(DWORD)+100*sizeof(PVOID));

    The following code is correct:

    malloc(offsetof(struct xx, Pointers) +100*sizeof(PVOID));
  • Do not pass (HANDLE)0xFFFFFFFF to functions such as CreateFileMapping. Instead, use INVALID_HANDLE_VALUE.
  • Use the proper format specifiers in printf or wsprintf. Use %p to print pointers in hexadecimal. This is the best choice for printing pointers. (Note that a future version of Visual C++® will support %I to print polymorphic data. Visual C++ will also support %I64 to print values that are 64 bits.)


Topic Not Found

No matching topic was found. Some possible reasons and remedies are as follows:

  • The file (.chm) that contains the destination topic is not in the expected location.

    Remedy: Rerun the MSDN Library Setup.

  • The file (.chm) that contains the current topic or destination topic is out-of-date and the link is no longer valid.

    Remedy: Install the latest version of the MSDN Library.

  • The MSDN Library CD is not in the CD-ROM drive.

    Remedy: Insert the MSDN Library CD into the CD-ROM drive.