Converting an Existing Application to 64-Bit

To assist you in converting an existing application project to 64-bit, Apple provides a script that does most of the work. The script performs in-place substitutions on source-code files according to a set of rules. However, after you run the script you still need to look at everything that changed or was flagged and fix it if necessary. This procedure described below works as well for existing framework projects, plug-in projects, or other projects.

The Conversion Procedure

Start by eliminating the build warnings that your project currently generates; this will make it easier to focus on warnings that are specific to 64-bit conversion problems. Also save a copy of the project in case something goes wrong in the conversion process and you need to start over. Then in a Terminal shell run the conversion script ConvertCocoa64 located in /Developer/Extras/64BitConversion/. You can run the script on specific source and header files, as in this example:

/Developer/Extras/64BitConversion/ConvertCocoa64 Controller.h Controller.m CustomView.h CustomView.h

Or you can run it on all .h and .m files in your project, as in this example:

/Developer/Extras/64BitConversion/ConvertCocoa64 `find . -name '*.[hm]' | xargs`

As the script runs, it does the following things:

After the script completes processing all files, run the FileMerge development application on each changed file and an unchanged version of the same file. FileMerge visually highlights each modified section of code. After reading “Things to Look For During Conversion,” evaluate each of these sections plus the warnings generated by the script and make further changes if necessary.

Remember that any 64-bit change you make should help preserve binary compatibility of your project executable on 32-bit systems. You should use the constructs in Table 3-1 to mark off sections of code conditionally for each architecture: PowerPC versus Intel and 32-bit versus 64-bit.

Table 3-1  Macros for architecture conditional code

Macro

Comment

#if __LP64__

PowerPC or Intel 64-bit. You should use this for most 64-bit changes.

#ifdef __ppc__

PowerPC 32-bit

#ifdef __ppc64__

PowerPC 64-bit

#ifdef __i386__

Intel 32-bit

#ifdef __x86_64__

Intel 64-bit

#ifdef __BIG_ENDIAN__

Big endian architectures

Things to Look For During Conversion

When the conversion script processes a source or header file, it often inserts warnings in the code to alert you to changes that you might have to make or pitfalls that you should try to avoid. These warnings are explained in the following sections.

Type Specifiers

Script action: Warns about potential problems; may generate false negatives.

Typically, in 32-bit code you use the %d specifier to format int values in functions such as printf, NSAssert, and NSLog, and in methods such as stringWithFormat:. But with NSInteger, which on 64-bit architectures is the same size as long, you need to use the %ld specifier. Unless you are building 32-bit like 64-bit, these specifiers generates compiler warnings in 32-bit mode. To avoid this problem, you can cast the values to long or unsigned long, as appropriate. For example:

NSInteger i = 34;
printf("%ld\n", (long)i);

Table 3-2 lists specifiers for formatting 64-bit types.

Table 3-2  Type specifiers for 64-bit types

Type

Specifier

Comments

NSInteger

%ld or %lx

Cast the value to long.

NSUInteger

%lu or %lx

Cast the value to unsigned long.

CGFloat

%f or %g

%f works for floats and doubles when formatting (but not scanning).

CFIndex

%ld or %lx

Cast the value to long.

long long

%lld or %llx

long long is 64-bit on both 32-bit and 64-bit architectures.

unsigned long long

%llu or %llx

unsigned long long is 64-bit on both 32-bit and 64-bit architectures.

The script also inserts warnings in the related case of scanning functions such as scanf, but these introduce an additional subtlety. You need to distinguish between float and double types, using %f for float and %lf for double. Don't use CGFloat parameter in scanf-type functions; instead use a double parameter and assign the double value to a CGFloat variable. For example,

double tmp;
sscanf(str, "%lf", &tmp);
CGFloat imageWidth = tmp;

Unkeyed Archiving

Script action: Warns of potential problems

The old style of Cocoa archiving relies on the sequence of encoded and decoded properties rather than keys as the means for identifying archived values; you must decode properties in the same order of their encoding. Developers are encouraged to move away from this old-style archiving to keyed archiving. However, applications may have to read old-style archives so they cannot abandon this key-less archiving altogether. And, from a 64-bit perspective, you cannot change these archives to accommodate larger values because that would make it impossible for older versions of the application to decode the archive.

When reading old-style archives, remember that integers encoded with the "i" and "l" (small L) type-encoding specifiers are 32-bit even on 64-bit architectures. To specify 64-bit integers you must use the "q" type-encoding specifier. But if you type an instance variable as an NSInteger value, the following decoding attempt won't work on 64-bit architectures:

// Header file
NSInteger bitsPerSample;  // instance variable
 
// Implementation file
[coder decodeValuesOfObjCTypes:"i", &bitsPerSample];

One way to get around this problem is to use a local variable for an intermediate value, then assign this value to the NSInteger instance variable after decoding.

// Header file
NSInteger bitsPerSample;
// Implementation file
int tmp;
[coder decodeValuesOfObjCTypes:"i", &tmp];
bitsPerSample = tmp;

You can use the same intermediate-variable technique for encoding NSInteger values (as 32-bit) as well as decoding them.

Instance variables typed as CGFloat present a similar problem since the "f" type-encoding specifier refers to float, not CGFloat. the workaround for this is the same as for NSInteger: use a local float variable to store the intermediate value, which you then assign to the CGFloat instance variable.

Cocoa is changing most enumerations to be based on the NSInteger type, which you should probably do in your own code too. Nevertheless, for old-style archiving you need to archive enum constants based on what the explicit or underlying type is.

Avoid using the @encode compiler directive in old-style archiving. The @encode directive generates a character string with the user-defined type stripped away. For example, during an LP64 build NSInteger resolves to long, but during a 32-bit build NSInteger resolves to int. The NSInteger semantics have been lost, and this could lead to bugs.

See “Encoding Verification” for information on a script that you can run to verify the consistency of old-style archiving code.

Keyed Archiving

Script action: Converts encodeInt:forKey: with encodeInteger:forKey: (NSInteger) and encodeFloat:forKey: with encodeDouble:forKey: (CGFloat).

You should examine the substitutions made by the script to see if any decoded values might be out of range. With keyed archiving it doesn't really matter if you encode integers with encodeInt:forKey:, encodeInt32:forKey:, or encodeInt64:forKey:. But, if on decoding, the archived value is larger than what 32-bit signed integers can hold, an NSRangeException is raised.

For most integral values, use of encodeInteger:forKey: and decodeIntegerForKey: are recommended. For values whose ranges are larger than what 32-bit signed integers can hold, encodeInt64:forKey: and decodeInt64ForKey: are the more appropriate choice, even on 32-bit systems.

In the rare case where an application wants to read 64-bit values in 32-bit processes, it can use decodeInt64ForKey: and be explicit about the type rather than using decodeIntegerForKey: (which reads back 32-bit on 32-bit systems).

Archiving Variables With NSNotFound Values

Script action: None.

NSNotFound is set to LONG_MAX in NSObjCRuntime.h, which has different values on 32-bit and 64-bit. Consequently, a variable archived with an NSNotFound value on a 32-bit system would have a different value if unarchived on a 64-bit system. In light of this, remove code that writes out NSNotFound to archives. If it is already written out, then you should check the 32-bit value explicitly when you read it in, even in a 64-bit application.

Instance Variables

Script action: Converts instance variables declared as int and unsigned int to NSInteger and NSUInteger, respectively. If integers are declared smaller than int and unsigned int (char or short), the script leaves them alone. Also changes float instance variables to CGFloat if they correspond to programmatic interfaces that deal in CGFloat values.

You should go over modified instance variables and verify if each should be NSInteger or NSUInteger. In some cases it might not be necessary to grow the size of instance variables because, even if the programmatic interface is 64-bit capable, the storage doesn't have to be. If that is the case, change the type of the instance variable back to int or unsigned int.

sizeof Operator and @encode Directive

Script action: Converts instances of sizeof and @encode where the argument is int, unsigned int, and float (to NSInteger, NSUInteger, and CGFloat, respectively). For other cases, generates warnings (with possible false negatives).

Go over each warning and make sure that no conversion needs to made manually.

Functions That Round Off Floating-Point Values

Script action: Converts calls of functions such as roundf and ceilf to (non-existent) functions of the form _CGFloatRound.

Because these functions take float arguments, any calls to these with CGFloat arguments need to be updated. Import the tgmath.h header and call round instead.

Arrays of Integers

Script action: Converts instances of int[] and unsigned int[] to NSInteger[] and NSUInteger[]. respectively.

If you have static or static const arrays of int or unsigned int, and the arrays are not exposed in API, you might want to change the type back for the memory savings.

Use of long and unsigned long

Script action: Marks each occurrence with a warning; does not convert.

Often leaving the type untouched is appropriate, but in some cases you may want to change it to an int to remain 32-bit.

Pointer Casts

Script action: Generates warnings for pointer casts that involve int or float values.

Limit Constants

Script action: Generates warnings for occurrences of INT_MAX, INT_MIN, UINT_MAX, FLT_MAX, and FLT_MIN since they might be involved in comparisons involving long or float values.

Switch (respectively) to the NSIntegerMax, NSIntegerMin, NSUIntegerMax, or the appropriate LONG_ or DBL_ variants.

Encoding Verification

As a further step toward ensuring 64-bit capable code, you can run a script that verifies the consistency of old-style (unkeyed) archiving code. This script, named CoderFormatVerifier, is installed in /Developer/Extras/64BitConversion/.

The script replaces NSCoder method invocations with type-checked function calls. On the command line, pass the script a directory—it should be copy of your source directory. The script recursively modifies all the .m and .M files in-place and then writes the function prototypes to standard output (stdout). You can then copy and paste the function prototypes into your prefix header, compile the code, and then inspect any compiler errors that you get. (Link errors are expected because the script only generates function prototypes.) Run the script without any arguments for usage information.