HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

Multiplatform code isn't a new occurrence or concept in software development. C and even C++ are cross-platform languages if you only use the standard libraries and refrain from using the platform-specific options offered by your compiler vendor. A recompile is required, but the source code can be made to work without modification. Other languages are also available across platforms, including scripting languages like REXX, PERL and Python; they allow us to skip the recompiled, generalized languages like BASIC and COBOL, and other small-following or specialized languages like Forth. So what does Java offer that we don't get from these other tools? Furthermore, what problems do we, as multiplatform developers, still need to solve? This article addresses these questions and presents a simple framework for developing multiplatform applications in Java.

To understand what Java gives us, we must first ask the question: "What are the traditional problems with writing cross-platform code using C or other languages?" I'll divide these issues into a few broad categories. The first and most obvious (at least for people with desktop PC backgrounds) is the user interface. Along with the workspace GUI issues, I'll also lump in other less visible "interfaces," often those between the application and the system that, while not presented directly to the end user, often differ across platforms. Some possible examples of this might be text formats (ASCII versus EBCDIC), permissions models (file or process permissions, etc.) or security models. Sometimes it's difficult to spot the assumptions you've made about all your application's interfaces to the user and the system.

The next problem is the availability of compiler implementations and third-party libraries used to supplement the functionality of the standard libraries, with features like database support and additional ADT implementations.

Finally, there is testing, the bane of multiplatform development. The testing effort for stand alone applications on different operating systems is fairly linear. However, when you add distributed application models, the size of the test matrix grows geometrically, as do the number of possible issues related to interoperability, latency and other factors.

Today: Multiplatform Development in Java vs Other Platforms
So how does Java assist with these obstacles? It certainly helps that the UI offers a uniform event-based GUI model with which we can develop applications having a cross-platform consistency. While there have been some complaints about the performance and/or look and feel of Java apps relative to those with platform-native interfaces, we've begun to see applications using the newest Java UI technology, with outstanding results.

Regarding compiler availability, Java delivers a standard compiler implementation that's available across most common development platforms. Plus, Java has recently introduced access to the source for creating modifications and implementations on new platforms. Differing C++ implementations have haunted developers for years. Even differing implementations on the same platform have caused problems. (Witness the VC++/Borland C++ incompatible implementations that persisted on Windows.) The consistency of Java compilers also helps significantly with the use of third-party libraries, ensuring that the chosen library will at least build successfully on your platform(s).

That's the good news.

What about the areas where Java doesn't help and may even hurt us? The foremost problem with multiplatform application development in Java is testing. It adds another complexity multiplier to the test matrix on virtual machine releases and, worse yet, multiple implementations of each release on some platforms. Further complexity is layered on by browser VMs (especially if parts of the application can run inside the browser and others outside) and browsers with plug-in VMs. With layer upon layer of multiplicative complexity from VM variations, the time required for testing can more than double on Java projects compared to the time using more traditional development tools. Some development efforts have seen testing resources jump from 20 to 50% and higher in the transition to Java. The only real solution is more compatible VM implementations. We can only hope that Sun, IBM and Microsoft hear our pleas: "Make the virtual machines more compatible!" Fortunately, progress is being made on this front, albeit more slowly than we'd like.

Putting aside the testing/VM compatibility issue, one primary obstacle remains: the system interfaces for which Java doesn't directly provide an abstraction layer. While it provides strong assistance with application GUIs via AWT and/or JFC, Java doesn't provide packaged solutions to many system interface problems. More are being tackled every day in new APIs and through Sun's new Java Extensions model, e.g., the Java Communication API for serial communication or the Java Cryptography Extension. This is a slow process, though, and there will always be interfaces that aren't covered. The solution here is simple (and certainly not new), although seldom well executed provide the abstraction layer for all nontrivial interfaces to the system that supports pluggable platform-specific implementations.

Platform Pack Solution
At InstallShield our products have to interface to the system using virtually every platform-specific interface in the spectrum, solving many of the problems that aren't directly addressed by Java. The installer has to support almost everything, because it must be able to set up a wide range of applications, from a simple text editor to an application server. In aggregate, the set of applications using our installer utilizes virtually every platform-specific service system-event logging, security and permissions, the file system and on and on ad infinitum.

Some may ask why we should be using platform-specific implementations rather than simply providing cross-platform Java implementations. First of all, it isn't always possible. For example, the creation of icons in desktop services: though it's often essential for rendering an application usable, Java doesn't directly support it, and there's no cross-platform solution.

Second, the use of native services is usually necessary for applications to interface successfully to the existing IT environment. Many new applications must work alongside existing native code, and to do so they often use the same system services as the older packages. Additionally, it's a great benefit for the system administrator to be able to administer applications using the standard tools for the platform. To accomplish this, the application must interface directly to those native tools. Of course, while it's desirable to leverage platform-specific features where possible, we also need to support platforms for which we haven't created platform-specific code, or for which any particular feature is simply not implemented.

When attempting to solve platform-specific problems, our natural tendency is to simply make it work with the platform we're targeting using JNI (this frequently happens when targeting Windows on the client side). While this handily solves the problem for each platform on a "one-at-a-time" basis, it gives up the "write once, run anywhere" promise of Java.

To fully realize this vision, what's needed is a robust and flexible architecture for implementing a set of platform-specific services in a cross-platform application, while also maintaining gracefully degraded performance on other systems. We call our solution "Platform Packs." For the most part, it follows the same pattern as Java's existing interfaces to platform services the separation of the definition of functionality in cross-platform Java classes from the implementation of that functionality in platform-specific VMs.

When attempting to solve platform-specific problems, our natural tendency is to simply make it work with the platform we're targeting using JNI (this frequently happens when targeting Windows on the client side). While this handily solves the problem for each platform on a "one-at-a-time" basis, it gives up the "write once, run anywhere" promise of Java.

To fully realize this vision, what's needed is a robust and flexible architecture for implementing a set of platform-specific services in a cross-platform application, while also maintaining gracefully degraded performance on other systems. We call our solution "Platform Packs." For the most part, it follows the same pattern as Java's existing interfaces to platform services the separation of the definition of functionality in cross-platform Java classes from the implementation of that functionality in platform-specific VMs.

The basic idea of the model is the creation and use of a set of named system services for the implementation of platform-specific items. At application startup, a "Service Manager" examines the system properties to determine the current running platform; then it finds an implementer for each service for that platform and performs any necessary startup for the service implementers, usually launching a service-implementer application, either one for each service or one for all of them.

Why a separate application? First, these platform-specific services are usually in a native executable generated with C/C++ or another native development tool and native libraries. Second, even if the service implementer is a set of Java classes, you may want to run the implementer on a separate machine or in a security context separate from that of the Java VM.

What if a service implementer for a given service doesn't exist for the current platform? The solution is to create a platform-neutral implementation for each service. When an unknown platform is encountered or an implementation of any particular service doesn't exist for the current platform, the platform-neutral service implementers are used. For some services, a platform-neutral implementation won't be available or obvious. However, in these cases the developer simply creates a nonfunctional shell service and lets the caller know that the service isn't implemented on any calls.

One example of a service requiring this approach is desktop services creating icons and such. There's no platform-neutral way of doing this (yes, I can hear your screams, CDE fans!), so the platform-neutral desktop service must simply return an E_SERVICE_NOT_IMPL error in any call to the service.

The following is a list, by no means all-inclusive, of other services you'll need to think about in your application:

  • Security (accommodation of the platform and/or application user model)
  • Command shell and environment services
  • File system access (this may be less trivial then you think file permission models differ, text file implementations differ, and some platforms may not even directly support files or paths in the way you're used to on Windows or UNIX)
  • Permissions (which may or may not need to be separated from the file system or security models depending on the needs of the application)
  • Registry/VPD (Vital Product Data) services (product install registration, etc.)
  • System event logging

Conclusion
The core of this strategy is simply to implement as much as possible in Java, including a pure Java implementation of each service area to form a platform-neutral pack. Additional platform-specific functionality can then be implemented in platform-specific native code on a service-by-service basis and activated at application startup by the service manager. Keep in mind that you may not require all services to be implemented for each pack. It's essential that the implementer of the Platform Pack for each environment be able to pick and choose when to use the platform-neutral implementation and when to use native implementation. An example from our product is the registry, which uses the platform-neutral implementation on Solaris and the native implementation on Windows, which maps directly to the Windows registry.

This allows use of the product across virtually all platforms, but with gracefully degraded functionality on those platforms for which no native functionality Platform Pack exists. Additionally, if your service manager allows the detection and creation of the set of available services at runtime, new applications built on the existing framework will be able to request and utilize new services without requiring modification to the service manager or any existing services.

I hope this discussion has provided a useful look at a successful strategy for the implementation of multiplatform applications in Java. Java clearly solves some of the biggest problems with development for heterogeneous systems, assisting us with consistent compilers, class libraries, some system interfaces and UI. While it doesn't solve all of our problems, a little forethought in the design of flexible system interfaces can greatly ease the burden of transitioning applications to new systems.

About the Author
Jim Wright lives in Santa Cruz and serves as the general manager of Java product development and director of developer relations at InstallShield Software Corporation. A consultant before joining InstallShield, Jim has worked in the areas of Windows application development, software installation, networked and widely distributed applications, and encryption systems. Jim can be reached at jamesw@installshield.com

 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: info@sys-con.com

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.