Software Development and the Bitness Challenge

Since 64-bit processors were introduced about ten years ago, they have become the default on both desktop and server machines. With a larger addressable process memory space, wider range of representable integers, and greater floating point precision, what’s not to like? For software developers that need to support both 32- and 64-bit machines, it’s a bit complicated.

Applications written in pure managed code (whether in .NET Intermediate Language or Java bytecodes)  will typically work just fine regardless of whether they’re running as 32-bit or 64-bit processes. However many applications also contain non-managed or native code that is targeted to a specific “bitness” (where “bitness” describes the distinction between 32-bit and 64-bit qualities of a platform, process, or system). In these cases, the developer needs to be careful that the right version of the non-managed code executes when the application is run, depending on the bitness of the running process. For software vendors, like JNBridge, which produce both self-contained applications and components that are integrated inside other users’ applications, the situation becomes even more tricky – the right version of the components needs to be included in the application, and it’s a common and easy mistake for users to include the components with the wrong bitness.  The user sees an error, and we get a support call. Better to reduce the chances of an error, and everybody will be happy.

After facing these issues for a number of years, we now have a workable solution, and our new JNBridgePro 7.0 release reflects this experience. We’d like to share what we’ve learned, so that other developers can successfully address the bitness challenge.

Create a single unified installer for both 32-bit and 64-bit scenarios. Developers’ first instinct is to create separate 32-bit and 64-bit builds and distribute separate installers for each. While this may seem logical, it leads to a lot of user confusion. In our experience, users faced with the decision as to whether to download the installer for the 32-bit or 64-bit version frequently download the wrong one. When they discover this error, they have to go back and download the other version, often after engaging support. Combining the 32- and 64-bit versions into a single installer is a win for the user and a win for us.

The challenge in building a single installer is that most installer generator packages either don’t allow 32-bit and 64-bit components to be combined in a single installer that would run on either system, or if they do support it, the resulting installation is too complicated. In particular, the usual technique of combining 32-bit and 64-bit installers, and a bootstrapper, into a single installer won’t work in our case, since we want 64-bit components to be installed on 32-bit machines, too. We resolved this by creating a single 32-bit installer (that is, one that would run on both 32-bit and 64-bit machines), and fooling the installer generator into thinking that the 64-bit components were simply “content,” so that the installer generator wouldn’t reject them. The “content” is all the 64-bit components packed into a single zip file. When the installer is run, a custom action extracts the 64-bit components then removes the zip file. While there are subtleties in getting this right (Google ‘advertised shortcut’ for pointers to issues that can arise), when done correctly this approach works perfectly and yields a single installer that runs on both 32-bit and 64-bit machines, and installs both 32-bit and 64-bit components on both machines. (Why do we need to install 64-bit components on 32-bit machines? Because, while the build machine may be 32-bit, the developer may be generating 64-bit applications.)

Ensure that users can use your components to create “Any CPU” applications. “Any CPU” applications are designed to automatically run as 32-bit processes on 32-bit machines and as 64-bit processes on 64-bit machines. This is very attractive as it allows the application to take advantage of whatever platform it runs on, and suggests that the code is portable. “Any CPU” implies that the code is completely managed, but there’s nothing to prevent unmanaged code, including third-party components, from being included. We’ve encountered scenarios where a developer used JNBridgePro to create an Any CPU application on their 64-bit development machine, successfully test it there, then ran into problems in deploying the application to a 32-bit machine. Situations like this suggest that developers should develop both 32-bit and 64-bit versions of their components or applications, but this is kicking the developer’s problem down the road, forcing the user to deal with using the right version. This should be as unacceptable to the software developer as it is to the user.

In order to allow users to create and run “Any CPU” applications, we changed JNBridgePro so that the appropriate 32-bit or 64-bit components would be loaded at run time depending on the bitness of the running process. However, platform vendors don’t make this easy. We discovered that the simplest way to test bitness at run time in .NET is to check the value returned by IntPtr.Size: 4 means you’re running as a 32-bit process; 8 means it’s a 64-bit process. (Note that the .NET alternative of calling Environment.Is64BitProcess won’t work in our case, since that API is only supported in .NET Framework 4.0 and later, and our components also need to be able to run in .NET Framework 2.0 through 3.5.) In Java, simply check the value of the system property os.arch. “x86” means that it’s a 32-bit process; “x86_64” means it’s a 64-bit process. These tests allow us to load the appropriate components at run-time, thereby supporting users who want to create applications that run anywhere, even though some of our components contain 32-bit or 64-bit targeted code.

Be careful using the Windows registry. When Microsoft created 64-bit Windows, they made the fateful decision to provide separate 32-bit and 64-bit registries, accessible only to respective 32-bit and 64-bit processes. The problem arises when the registry is used to share information between applications. Information deposited by  a 32-bit application is inaccessible to a 64-bit application, and vice versa. The answer is to only use the registry for information used by a single application, and only when the application’s bitness on that machine will always be the same. Save the information shared between applications in files; do not use the registry. JNBridge used to store information in the registry, but this became a constant source of support headaches once 64-bit platforms were introduced. Now that we store information in files, not the registry, these bitness-related headaches have gone away.

In conclusion: If all the processes in the world were 64-bit processes, the problems I’ve mentioned above wouldn’t exist.  However, as long as 32-bit processes and platforms still exist and must be supported, software developers must be careful and watch out for pitfalls that can trip up users. When developers adhere to the guidelines discussed above, users’ bitness problems should disappear, to the great benefit of both users and software support teams.