Rules and Standards

CAA V5 C++ 64-bit Operating System Support

Rules, hints, and tips to help your applications run on 64-bit operating systems

Technical Article

Abstract

CATIA, DELMIA, and DMU solutions run on the AIX and Windows operating system in 64-bit mode. If you are interested with 64-bit, you can check your application to build and run using the AIX 64-bit operating system.

This article describes the 64-bit operating system characteristics and their consequences on you current application code. It also gives some tips and rules about compiling, reviewing, and debugging your code, along with the V5 generic basic types that help your code run on both 32-bit and 64-bit operating systems. Finally, some common errors found when running in 64-bit, but coming from 32-bit coding, are explained along with their solutions.


What Does 64-bit Means?

A 64-bit operating system uses memory addressed using 64 bits instead of 32, that is, theoretically 16 EB (or 264 bytes, 1 exabyte being 260 bytes) instead of 4 GB (or 232 bytes, 1 gigabyte being 230 bytes). This increased virtual address space enables you:

Because of hardware or software restrictions, some platforms might not support the full 64-bit virtual address space. The actual limit is then below 16 EB, and is usually measured in TB (terabytes or 240 bytes). For example, Microsoft announces that 64-bit Windows NT supports 16 TB (or 244 bytes, 1 terabyte being 240 bytes) of memory, that is 1024 times more than 32-bit Windows NT.

All the components involved in a 64-bit application must be native 64-bit.

With AIX, an application built:

With Windows, an application built:

[Top]

What Are the Differences between 32-bit and 64-bit?

Unix and Windows both tried to minimize changes needed in their existing source code, which resulted in different definitions of some basic types. In particular, long was defined as a 64-bit type on Unix and as a 32-bit type on Windows.

The main changes are the size of some basic types:

Basic Types Lengths in 32-bit and 64-bit
Type 32-bit (*) 64-bit Unix 64-bit Windows Definition
int 32 32 32 Integer
long 32 64 32 Long integer
void * 32 64 64 Pointer
size_t 32 64 64 Data type for objects declared to store result of sizeof operator.
ssize_t 32 64 64 Data type used to represent the sizes of blocks that can be read or written in a single operation. Similar to size_t, but must be signed
off_t 32 64 32 Arithmetic data type used to represent file sizes
time_t 32 32 64 Arithmetic data type representing calendar time

(*): Common to Windows and UNIX.

[Top]

Getting Ready for 64-bit

All the components involved in a 64-bit application must be native 64-bit.

To enable your applications to go on running with 32-bit operating systems, the advice in this article rely on the following migration hypotheses:

Note: Having a very high automatic test code coverage is very helpful for 64-bit migration. Thus, once build time is successful, you will be able to estimate the time needed for run time convergence. But the most important is that you will be able to check that you do not introduce any regression either in 32-bit or in 64-bit.

To get ready for 64-bit, you should:

  1. Compile your code in 64-bit mode and correct the errors and warnings
  2. Review your code according to the code review rules: find portions of code that match the identified patterns and apply the corrections advised for such patterns
  3. Debug your code when finding runtime problems.

Once 64-bit pure convergence has been achieved, validate that 64-bit streamed documents are readable in 32-bit mode.

You can refer to [1] and [2] for additional information about Windows 64-bit and porting applications to 64-bit.

Compiling Your Code

The mkmk environment uses the _MkmkOS_BitMode environment variable set to 64 to compile and build code in 64-bit. On a 64-bit machine, this variable is set by tck_profile automatically.

The 64-bit AIX and Windows operating system related codes in the directory are:

IBM AIX aix_a64
Microsoft Windows win_b64

To build your frameworks in 64-bit, type:

In the build log, examine the errors or warnings that show 64-bit problems.

Some preprocessor flags are available to discriminate between the different types of processors or operating systems:

_WINDOWS_SOURCE To compile for Microsoft Windows
_AIX_SOURCE To compile for IBM AIX
_HPUX_SOURCE To compile for HP HP-UX
_SOLARIS_SOURCE To compile for Sun Solaris
PLATEFORME_DS64 To compile in native 64-bit mode. Valid with all operating systems
_ENDIAN_LITTLE To compile for a Little Endian processor
_ENDIAN_BIG To compile for a Big Endian processor

_ENDIAN_LITTLE and _ENDIAN_BIG are used to discriminate between Little Endian and Big Endian processors. Currently, _ENDIAN_LITTLE is enabled only for Intel or AMD processors (32- and 64-bit versions). Use these flags instead of _WINDOWS_SOURCE which is dedicated to Windows platforms and not available for other platforms.

Check the compilation errors and warnings against the Most Common Errors and Solutions. You can also use the portable V5 Generic Basic Types.

Reviewing Your Code

You can check your code against the Most Common Errors and Solutions

Debugging Your Code

Once the application builds, run your ODTs to find out run time problems, and correct them.

Using mkmk Commands for Windows 32-bit Code on Windows 64-bit Platforms

As a default behavior, mkmk commands, such as mkmk, mkodt, mkrun, and mkCheckSource allow 64-bit code to run on 64-bit platforms. To use these tools to build, test, run, and check 32-bit code on a 64-bit platform, set the _MkmkOS_BitMode variable to 32 before running the tck_init profile:

set _MkmkOS_BitMode=32
t:tck_init.bat      // assuming the RADE tool installation is mapped to the t: drive

For example, to build your frameworks in 32-bit mode on a 64-bit machine, type:

set _MkmkOS_BitMode=32
t:tck_init             // assuming the ENOVIA Studio tool installation is mapped to the t: drive
tck_profile MyTCK
adl_ch_ws MyWorkspace
mkmk -auw

Deploying your Application

To deploy you application, refer to [3].

[Top]

The V5 Generic Basic Types

Some generic basic types with explicit names help you to write code independent from the platform without using the preprocessor flags described above. Theses types (and macros) allow you to port your code without runtime impacts on 32-bit because you can modify your code keeping it identical when compiling on 32-bit platforms.

For example:

These generic basic types are available in the CATDataType.h file of the SpecialAPI framework:

Types UNIX Windows
32-bit Length 64-bit Length 32-bit Length 64-bit Length
CATINT32 int 4 int 4 int 4 int 4
CATUINT32 unsigned int 4 unsigned int 4 unsigned int 4 unsigned int 4
CATLONG32 long 4 int 4 long 4 long 4
CATULONG32 unsigned long 4 unsigned int 4 unsigned long 4 unsigned long 4
CATLONG64 long long 8 long long 8 _int64 8 _int64 8
CATULONG64 unsigned long long 8 unsigned long long 8 unsigned _int64 8 unsigned _int64 8
LONG long 4 int 4 long 4 long 4
ULONG unsigned long 4 unsigned int 4 unsigned long 4 unsigned long 4




CATINTPTR int 4 intptr_t 8 __w64 int 4 INT_PTR 8
CATUINTPTR unsigned int 4 uintptr_t 8 __w64 unsigned int 4 UINT_PTR 8
CATLONGPTR long 4 intptr_t 8 __w64 long 4 LONG_PTR 8
CATULONGPTR unsigned long 4 uintptr_t 8 __w64 unsigned long 4 ULONG_PTR 8




CATLONGINT long 4 long 8 long 4 long 4
CATULONGINT unsigned long 4 unsigned long 8 unsigned long 4 unsigned long 4

Note: Lengths are expressed in bytes.

Avoid using long because its size differs from Windows to UNIX in 64-bit. Use LONG or ULONG instead.

In addition, some macros help to convert pointers to different types, and the reverse:

Macro Equivalent to Length
32-bit 64-bit
CATPtrToINT32(p) ((CATINT32)(CATLONGPTR)(p)) 4 4
CATPtrToUINT32(p) ((CATUINT32)(CATULONGPTR)(p)) 4 4
CATPtrToLONG32(p) ((CATLONG32)(CATLONGPTR)(p)) 4 4
CATPtrToULONG32(p) ((CATULONG32)(CATULONGPTR)(p)) 4 4
CATINT32ToPtr(p) ((void*)(CATLONGPTR)(p)) 4 8
CATUINT32ToPtr(p) ((void*)(CATULONGPTR)(p)) 4 8
CATLONG32ToPtr(p) ((void*)(CATLONGPTR)(p)) 4 8
CATULONG32ToPtr(p) ((void*)(CATULONGPTR)(p)) 4 8

When you modify a virtual method signature, for example moving an int to a CATINTPTR, you have to propagate the same modification on all the re-definitions of this method (of course in base classes too). This is mandatory for 64-bit runtime. If you don't do this you break polymorphism mechanisms on 64-bit runtime. Concerning 32-bit, even if you don't migrate these re-definitions the runtime works because in fact you didn't change the signature.

[Top]

Most Common Errors and Solutions

The most common errors that you will have to deal with may be due to:

[Top]

Using C++ ANSI

This deals with the scope of variables declared in for statement.

Note: This is not dedicated to 64-bit, but happen with the compiler used. The same behavior will occur in 32-bit also when using VC 7.1.

The new ANSI C++ standard specifies that variables declared as in for (int i=0; ...) have a scope local to the for statement. Nevertheless, older compilers like Visual C++ 5.0 use the older concept that the scope is the enclosing group. Below, are two possible problems arising from this change and their recommended solutions.

for( int i=0; i<N; i++ ) ...
if ( i == N ) ...

Since i is used outside of the for scope, the compilation fails with compilers compliant with the new ANSI C++. If you want to use the variable after the for statement, declare it before the for statement:

int i;
for ( i=0; i<N; i++ ) ...
if (i==N) ...

The compilation fails also when using the same variable in multiple for loops, such as:

for( int i=0; i<N; i++ ) ...
for( i=0; i<M; i++ ) ...

You can declare the variable outside of the for statement:

int i;
for( i=0; i<N; i++ ) ...
for( i=0; i<M; i++ ) ...

You can also use a variable specific to each for:

for( int i=0; i<N; i++ ) ...
for( int j=0; j<M; j++ ) ...

[Top]

Data Alignment

Data alignment deals with data location in memory. 64-bit compilers align data, that is, locate data at an address that is a multiple of its length, provided this data is less or equal to 8 bytes in length. Alignment defaults may occur in buffers and can throw exceptions and/or crash the application much more often than in 32-bit.

For more information, refer to the MSDN article about data alignment [4].

Example: Computing 32-bit hash key from a pointer resulting from an allocation:

unsigned int CATMyHashIt( void * iObj )
{
  unsigned int key=0;
  if ( iObj )
  {
    #ifdef PLATEFORME_DS64
    key = CATPtrToUINT32(iObj) >> 3;  // Generally in 64-bit allocations are aligned on a boundary of 8 bytes
    #else
    key = CATPtrToUINT32(iObj) >> 2;  // Generally in 32-bit allocations are aligned on a boundary of 4 bytes
    #endif
  }
  return key;
}

In 64-bit, the key is the pointer cast to an unsigned int the length of which is 4 bytes. This unsigned int is then processed by the right bitwise shift operator that shifts it right 3 positions, that is, divides it by 8. In 32-bit mode, it is divided by 4.

[Top]

Storing a Pointer as an int or a long

When casting a pointer to a 32-bit basic type like an int, you have to know if it is a real 64-bit address or not. If not you can use CATPtrTo prefixed macros to make your code compile without warning. Otherwise you have to use the 64-bit equivalent type, for example CATINTPTR instead of an int thus you do not modify the 32-bit view of your code.

Example 1: Typical compilation error or warning occurring when setting a send/receive callback that passes an integer to the calback method:

int info = ... ;
 _selCallBack = AddAnalyseNotificationCB (label,
                                          label->GetActivateNotification(),
                                          (CATCommandMethod)&MyStateCommand::MyCallback,
                                          (CATCommandClientData) info);
 ...
 void MyStateCommand::MyCallback(CATCommand* iFromClient, CATNotification* iNotif, CATCommandClientData iData)
 {
    int ipos = (int) iData;
    ...
}

The integer value is passed to the callback method MyCallback thanks to the CATCommandClientData, that is, a pointer to void. As usual in 32-bit, the int and the pointer have both the same size of 4 bytes. In 64-bit mode, the pointer is 8-byte long, but the int remains 4-byte long. Even if there is no loss of data when putting 4 bytes in 8, and then retrieving the first 4 bytes from the _ bytes of the pointer, compilers will issue errors or warnings. To fix them, write:

int info = ... ;
 _selCallBack = AddAnalyseNotificationCB (label,
                                          label->GetActivateNotification(),
                                          (CATCommandMethod)&MyStateCommand::MyCallback,
                                          (CATCommandClientData) CATINT32ToPtr(info));
 ...

 void MyStateCommand::MyCallback(CATCommand* iFromClient, CATNotification* iNotif, CATCommandClientData iData)
{
    int ipos = CATPtrToINT32(iData);
    ...
}

First cast the int as a 32-bit pointer using CATINT32ToPtr in the AddAnalyseNotificationCB method that sets the callback, and then cast the pointer back to an int thanks to CATPrtToINT32 in the callback method.

Example 2: When casting a pointer to pointer into an (unsigned int *) for example, compilers do not issue any warning or error to help migration in case of non 64-bit portable instruction.

MyClass ** ppClass = ...;
unsigned int * uiClass = (unsigned int *) ppClass;

 for ( int i=0; i<size ; i++ )
{
    MyClass * pClass = ppClass[i];
    uiClass[i] = pClass->Method(); // destination zone is invalid and 32-bit are set instead of 64-bit.
}

To fix this problem:

MyClass ** ppClass = ...;
CATUINTPTR * uiClass = (CATUINTPTR *) ppClass;

 for ( int i=0; i<size ; i++ )
{
    MyClass * pClass = ppClass[i];
    uiClass[i] = pClass->Method();
}

[Top]

Casting Non-Fixed Size Basic Types by Reference or Address to Make the Code Compile

unsigned int count = 0;
...
iValue.ConvertToUTF16( utf16String, (size_t *) & count );

To fix this problem:

size_t count = 0; // You can also use an intermediate temporary variable of size_t type.
...
iValue.ConvertToUTF16( utf16String, & count );

To avoid this kind of problems, check all the casts by reference or address for the following types:

Most of time, use an intermediate local variable of the right type!

Note: If your application runs specifically on Windows, casting an int as (long&), for example, is not a problem because these types are both 32-bit length. In this case you are interested in casting size_t to int or long.

[Top]

Using Unions

Avoid unions as far as possible. Do not use types in unions whose length is different from an operating system to another.

Unions are sometimes used to manipulate pointers as integers. For example:

union {
  void * ptr;
  unsigned int ptrAsInt;
}

This compiles in 32-bit because the pointer and the unsigned int sizes are both 4 bytes. This does not compile with 64-bit, because the pointer size passes to 8 bytes while the unsigned int size remains 4 bytes. To fix this problem, use CATUINTPTR that stores a pointer using the appropriate types depending on the operating system is Windows or UNIX in either 32-bit or 64-bit mode:

union {
  void * ptr;
  CATUINTPTR ptrAsInt;
}

Another union example is an equivalence between a simple type, say an unsigned long, and a table of chars:

union {
  unsigned long lwEq;
  char bytes [4];
}

This compiles in 32-bit because the unsigned long and a table of chars sizes are both 4 bytes. This does not compile with UNIX 64-bit, because the unsigned long size passes to 8 bytes while the table of chars size remains 4 bytes. To fix this problem, use CATULONG32 that stores an unsigned long using its appropriate size depending on the operating system is Windows or UNIX in either 32-bit or 64-bit mode:

#include “CATDataType.h”
...
union {
  CATULONG32 lwEq;
  char bytes [4];
};

[Top]

Multiplying an int by unsigned int

Pay attention to results you may not expect when multiplying an int by an unsigned int, because the result, as specified by the C ANSI, is unsigned int!

For example:

unsigned int size = ...;
 int i = -1;
 void * tab;
 tab [ icurrent + size * i ]  access to unexpected memory area.

Multiplying size by i (-1) produces an unsigned int. The result of icurrent + size * i is greater than icurrent, while the result is expected as smaller. An unexpected memory area might then be accessed.

[Top]

Invalid Format String Conversions

To print out a pointer or an integer value, use the appropriate format specifiers.

Pointer Format Specifier

In case of printing a pointer value, do not use %x or %X, but use %p instead.

The following code will print a truncated pointer value:

printf("%x", ptr_value) // %x is a 32-bit field and will truncate the pointer

To fix this problem:

printf("%p",ptr_value)  // change to %p to display the full pointer value
32-bit Integer Format Specifier

With UNIX only, to display or read an unsigned int or an int, do not use %u or %d with printf or scanf, but use %lu or %ld instead.

In the following example, the argument is incompatible with the corresponding format string conversion:

CATUINTPTR M = ...
CATINTPTR  N = ...
fprintf( stdout, "size=%u number=%d \n", M, N );

To fix this problem:

CATUINTPTR M = ...
CATINTPTR  N = ...
fprintf( stdout, "size=%lu number=%ld \n", M, N );

[Top]

Explicit Constant Typing

long a = 0xffff;    // Assumes a long is 4 bytes
void *ptr = 0xffff; // Assumes a pointer is 4 bytes

To fix this problems, for instance you can extend these values:

#ifdef PLATEFORME_DS64
long a = 0xffffffff;
void *ptr = 0xffffffff;
#else
long a = 0xffff;
void *ptr = 0xffff;
#endif

[Top]

Implicit Function Prototype in C Files

When you do not define the signature of a function in a C file, the compiler assumes the function returns an int.

This problem is not specific to 64-bit. Forgetting including math.h is a classical problem (in 32-bit as well as in 64-bit) because then using functions returning a double will be considered as returning an int and the result is truncated before being copied to the destination double variable.

With 64-bit, this problem also applies to functions returning pointers (or long integers), like malloc which is defined in stdlib.h, or strdup which is defined in string.h.

In addition, this problem does not only concern standard C libraries but also your own libraries, so you have to include or explicitly define the right signature of all used functions, particularly those that do not return an int.

[Top]


References

[1] 64-bit Windows
[2] General Porting Guidelines
[3] CAA Partner Product Installation
[4] Data Alignment with 64-bit Windows
[Top]

In Short

This article helps you prepare your applications to support 64-bit operating systems. The proposed methodology consists in setting the appropriate 64-bit environment, compiling your application to first analyze and correct the build time errors or warnings. When the application builds, then look for and correct run time errors shown when replaying your ODTs.

This article explains the differences between 32-bit and 64-bit operating systems, explains the most common errors found when preparing applications for 64-bit along with the appropriate solutions.

[Top]


History

Version: 2 [Apr 2008] Document updated for 32-bit code built on a 64-bit machine with Windows
Version: 1 [May 2004] Document created
[Top]

Copyright © 1994-2008, Dassault Systèmes. All rights reserved.