Merging Java and Win32: A new way to develop Windows applications

Learn how to write Win32 applications in Java instead of C++ -- and save yourself some time and effort!

The news media has focused attention on a number of mergers in recent weeks. Banks, automotive corporations, and retail chains have announced that they are merging. Can you imagine the shock if Sun Microsystems and Microsoft ever decide to merge? Well, I don't think we should hold our breath. I do, however, think that Sun and Microsoft could learn a thing or two from each other. After all, both companies have developed good products -- namely Java and Win32. In my opinion, the Java learning curve is much shorter than the C++ learning curve. At the same time, Win32 is one important reason why Microsoft has Windows 95/NT running on some umpteen million PCs. It seems only natural to merge Java and Win32 to give give developers the edge they need to create better Windows applications in shorter amounts of time. That's the focus of this article.

In the beginning...

The first Windows applications were written in the C language. While C was okay for small applications, developers found it difficult to use this language to organize larger applications. The problem centered around the Windows messaging model and the fact that C is a structured rather than an object-oriented language. Traditional applications using C would create a main window and assign a callback function (known as a window procedure) to this window. Whenever anything of consequence happened to this window, Windows would fire a message to the window by calling the window procedure. The window procedure would respond by first identifying the message via a huge switch-case statement and then process the message. As is often the case, state would need to be saved via local static variables or global variables. A large application could result in many such variables. This paradigm worked well for smaller applications but proved to be detrimental to larger applications. Something had to be done.

The C language evolved from a structured language into an object-oriented language -- a language called C++. The nice thing about an object-oriented language is that it gives developers the ability to model real-world entities in a more natural way by using objects.

A few years ago, Microsoft released a tool for developers who wanted to create Windows applications using C++. This product became known as Visual C++. One of the features introduced with Visual C++ was an application framework known as Microsoft Foundation Classes (MFC). The MFC framework is a collection of C++ classes, written and tested by Microsoft's developers, that implements a lot of basic Windows functionality. Many software concepts -- from toolbars and status bars to a document-view model based on the Model-View-Controller architecture -- have been implemented in MFC. The idea behind MFC is to save time during development by using MFC code for most of the application and then extending MFC to provide that application's unique capabilities -- via the fundamental object-oriented concepts of encapsulation, inheritance, and polymorphism.

However, developing software with MFC is not an easy task. In order to write today's Windows applications using C++ and MFC, developers need to have a good understanding of object-oriented programming concepts, C++ syntax and peculiarities, the Windows APIs, and MFC.

Ideally, developers need a single language and platform that allows them to write applications just once and then deploy them everywhere. In an attempt to meet this need, Sun has implemented platform-neutral versions of many Windows APIs in addition to APIs unique to Java (such as Java Card). APIs dealing with file management, mail, help, multimedia, and security have counterparts in the Windows world. This results in one major benefit to Windows developers: Instead of learning a lot of Windows APIs along with C++ and MFC, developers can focus on learning Java and its APIs. Then, they can use Java to develop Windows applications. Here's how.

The Invocation API

The designers of Java came up with a mechanism for getting Java code to talk to C++ code. This mechanism uses a collection of C++ APIs known as the Java Native Interface (JNI). Several of these APIs have been brought together, and are collectively known as the Invocation API.

The Invocation API consists of several JNI functions that enable the developer to embed the Java virtual machine (JVM) into an arbitrary native application. With JVM embedded, the native application has access to the entire JVM by making JNI calls.

The JVM is created via a call to the JNI_CreateJavaVM () function. This function takes a pointer to a JDK1_1InitArgs structure as an argument. This structure provides default settings for the JVM. The defaults can be overridden.

To obtain the default settings, another JNI function, JNI_GetDefaultJavaVMInitArgs (), must be called. This function takes a pointer to the JDK1_1InitArgs structure as an argument. A typical calling sequence appears in the following listing:

JDK1_1InitArgs vm_args;
vm_args.version = 0x00010001;
JNI_GetDefaultJavaVMInitArgs (&vm_args);

The version field must be set prior to calling JNI_GetDefaultJavaVMInitArgs (). This field ensures that the correct JVM is used by the application. A value of 0x00010001 encodes the major version number of the required JVM in the high 16 bits and the minor version number in the low 16 bits. The 0x00010001 value means that any JVM whose version number is 1.1.2 or higher will be embedded into the application.

Several interesting fields comprise the JDK1_1InitArgs structure, but the only field we'll mention in this article is the field known as classpath. This field is important because it tells the JVM where classes.zip and the application class files reside.

Once the JDK1_1InitArgs structure has been initialized, the JVM can be created via a call to JNI_CreateJavaVM (), as shown in the following listing:

JavaVM *jvm;
JNIEnv *env;
rc = JNI_CreateJavaVM (&jvm, &env, &vm_args);

At this point, JNI functions FindClass () and CallStaticVoidMethod () would be called to find the appropriate Java starting class and the starting main method.

Once the JVM is no longer required, it is destroyed by a call to DestroyJavaVM (), as in the following listing.

jvm->DestroyJavaVM ()

So, how does the Invocation API allow us to create Win32 applications using Java? The following example provides an answer.

An example

I decided to create a Win32 console application similar to PKZIP, but my application would be a little simpler. It would only provide the ability to list all files in a zip archive and extract files. My application would be launched from the command line using the following syntax:

c:\>zip [-x file] zip

whereby -x is the extraction flag, file is the name of the file to extract, and zip is the name of the archive with or without a zip extension.

The following listing shows the C++ source code zip.cpp. This code implements the ZIP executable driver. This driver loads the JVM, parses the command line arguments, locates the ZIP class file, locates the main method within the ZIP class file, launches the main method (passing the arguments list to this method), and unloads the JVM.

// ===================================================
// zip.cpp
//
// ZIP Executable Driver
//
// Supports Java Virtual Machine (JVM) 1.1.2 or higher
// ===================================================
#include <jni.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#define BUFSIZE     80
// ================================================
// Handler
//
// Console Control Handler
//
// Ignore all attempts to shutdown the application.
//
// Arguments:
//
// dwCtrlType - control event type
//
// Return:
//
// TRUE (ignore event)
// ================================================
BOOL Handler (DWORD dwCtrlType)
{
   return TRUE;
}
// =======================================
// main
//
// Zip Executable Driver Entry Point
//
// Arguments:
//
// argc - number of command line arguments
// argv - array of command line arguments
//
// Return:
//
// 0 (success) or 1 (failure)
// =======================================
int main (int argc, char *argv [])
{
   int i;
   jint ret;
   JNIEnv *env;
   JavaVM *jvm;
   jclass clazz;
   jmethodID mid;
   JDK1_1InitArgs vm_args;
   char szBuffer [BUFSIZE], szClassPath [BUFSIZE * 2 + 15];
   // Prevent application from shutting down due to Ctrl-Break or Ctrl-C keypresses,
   // window close button clicks, user logoff, or system shutdown.
   SetConsoleCtrlHandler ((PHANDLER_ROUTINE) Handler, TRUE);
   // Get default initialization arguments for JVM version 1.1.2 or higher.
   vm_args.version = 0x00010001;
   JNI_GetDefaultJavaVMInitArgs (&vm_args);
   // Tell the JVM where to find the application class files and classes.zip.
   GetPrivateProfileString ("CONFIG", "PATH", ".", szBuffer, 80, "zip.ini");
   wsprintf  (szClassPath, "%s;%s\\classes.zip;", szBuffer, szBuffer);
   vm_args.classpath = szClassPath;
   // Attempt to create a JVM instance.
   if ((ret = JNI_CreateJavaVM (&jvm, &env, &vm_args)) < 0)
   {
    fprintf (stderr, "Can't create JVM.  Error: %ld\n", ret);
    return 1;
   }
   // Process command-line arguments.
   jstring jstr = env->NewStringUTF ("");
   jobjectArray str_array = env->NewObjectArray (argc - 1, 
                                                 env->FindClass ("java/lang/String"), 
                                                                 jstr);
   for (i = 1; i < argc; i++)
   {
        if ((jstr = env->NewStringUTF (argv [i])) == 0)
        {
         fprintf (stderr, "Out of memory\n");
         return 1;
        }
        env->SetObjectArrayElement (str_array, i - 1, jstr);
   }
   // Attempt to locate zip class.
   if ((clazz = env->FindClass ("zip")) == 0)
   {
       fprintf (stderr, "Can't locate the zip class.  Exiting...\n");
       return 1;
   }
   // Attempt to locate the zip class main method.
   if ((mid = env->GetStaticMethodID (clazz, "main", "([Ljava/lang/String;)V")) == 0)
   {
       fprintf (stderr, "Can't locate the main method.  Exiting...\n");
       return 1;
   }
   // Launch main method.
   env->CallStaticVoidMethod (clazz, mid, str_array);
   // Destroy the JVM instance.
   jvm->DestroyJavaVM ();
   return 0;
}

Note the call to the Win32 GetPrivateProfileString () function. This function looks for a file called zip.ini (which would be located in the Windows directory -- usually c:\windows under Windows 95 or c:\winnt under Windows NT). The purpose of this file is to hold the path where the ZIP application is installed. The JVM will look in this location for classes.zip and application class files (no matter where the ZIP application is called from).

Another item to note is a call to the SetConsoleCtrlHandler () Win32 API. This API prevents Ctrl-C or Ctrl-Break key presses -- in addition to other events -- from stopping the application before it finishes. This may or may not be desirable, depending on the application.

The ZIP application is written in Java. It gives users the ability to view the contents of zip archive files, as well as the ability to extract individual files from these archives. The following listing contains the source code to ZIP.

// ========
// zip.java
// ========
import java.io.*;
import java.util.Enumeration;
import java.util.zip.*;
public class zip
{
   // ==================================================================
   // dump_zip
   //
   // Dump the contents of the zip file (the file names of zipped files)
   // to the standard output device.
   //
   // Arguments:
   //
   // zip_name - name of zip archive
   //
   // Return:
   //
   // none
   // ==================================================================
   public static void dump_zip (String zip_name)
   {
      Enumeration list = null;
      ZipEntry ze = null;
      ZipFile zf = null;
      // Check to see if zip_name has an extension.
      // If not (no period character detected) then
      // attach ".zip" to the file name.
      if (zip_name.indexOf ('.') == -1)
          zip_name = zip_name + ".zip";
      try
      {
          zf = new ZipFile (zip_name);
      }
      catch (ZipException e1)
      {
         System.err.println ("ZIP exception: " + e1);
      }
      catch (IOException e2)
      {
         System.err.println ("IO exception: " + e2);
      }
      list = zf.entries ();
      while (list.hasMoreElements ())
      {
         ze = (ZipEntry) list.nextElement ();
         System.out.println (ze.getName ());
      }
   }
   // ===================================
   // extract_zip
   //
   // Extract file from zip archive.  
   //
   // Arguments:
   //
   // zip_name  - name of zip archive
   // file_name - name of file to extract
   //
   // Return:
   //
   // none
   // ===================================
   public static void extract_zip (String zip_name, String file_name)
   {
      BufferedInputStream bis = null;
      byte buf [] = new byte [1024];
      Enumeration list = null;
      FileOutputStream fos = null;
      int ln = 0;
      int sz = 0;
      ZipEntry ze = null;
      ZipFile zf = null;
      // Check to see if zip_name has an extension.
      // If not (no period character detected) then
      // attach ".zip" to the file name.
      if (zip_name.indexOf ('.') == -1)
          zip_name = zip_name + ".zip";
      try
      {
          zf = new ZipFile (zip_name);
      }
      catch (ZipException e1)
      {
         System.err.println ("ZIP exception: " + e1);
      }
      catch (IOException e2)
      {
         System.err.println ("IO exception: " + e2);
      }
      list = zf.entries ();
      while (list.hasMoreElements ())
      {
         ze = (ZipEntry) list.nextElement ();
         sz = (int) ze.getSize ();
         // Compare archive file with file name argument
         // in a case-insensitive manner.
         if (ze.getName ().toUpperCase ().endsWith (file_name.toUpperCase ()))
         {
             try
             {
                bis = new BufferedInputStream (zf.getInputStream (ze));
                fos = new FileOutputStream (new File (file_name));
                while (sz > 0 &&  // workaround for bug
                       (ln = bis.read (buf, 0, Math.min (1024, sz))) != -1)
                {
                   fos.write (buf, 0, ln);
                   sz -= ln;
                }
                bis.close ();
                fos.flush ();
             }
             catch (IOException e3)
             {
                System.err.println ("IO exception: " + e3);
                return;
             }
             System.out.println ("File: " + file_name + " extracted");   
             return;
         }
      }
      System.out.println ("Not found");
   }
   // =============================================
   //
   // main
   //
   // Program entry point.
   //
   // Arguments:
   //
   // args - array of command line argument strings
   //
   // Return:
   //
   // none
   // =============================================
   public static void main (String [] args)
   {
      System.out.println ("zip v1.0\n");
      if (args.length == 1)
          dump_zip (args [0]);
      else
      if (args.length == 3 && args [0].equals ("-x"))
          extract_zip (args [2], args [1]);
      else
      {
          System.err.println ("usage: zip [-x file] zip");
          System.exit (1);
      }
   }
}

Putting it together

Follow these steps to build the ZIP application.

Step 0: The following assumptions have been made:

  • The ZIP application is built using JDK 1.1.5 and Visual C++ 5.0
  • JDK 1.1.5 is installed in the c:\jdk1.1.5 directory
  • The following project settings are in place
    • A release version is being built
    • The Project Settings dialog box includes the line:

      /I "c:\jdk1.1.5\include" /I "c:\jdk1.1.5\include\win32"

      in the Project Options list box

    • javai.lib has been included in the workspace

Step 1: Identify all required files

C++:

  • zip.cpp zip executable driver source file
  • zip.dsp zip executable driver project file
  • zip.dsw zip executable driver workspace file
  • zip.exe zip executable driver deployment file
  • javai.lib import library for JVM

Java:

  • zip.class zip application deployment file
  • zip.java zip application source file

Miscellaneous

  • classes.zip necessary Java classes
  • javai.dll Java Interpreter (JVM)
  • zip.ini support file containing path information

Step 2: Create zip.ini and copy this file into the Windows directory

The zip.ini file contains a single section called CONFIG. There is one entry in this section, PATH. The PATH entry contains the path (without a trailing backslash character) where the ZIP application has been installed. This is shown in the following listing.

[CONFIG]
PATH=c:\zip

Step 3: Create a directory for the ZIP application (ex: c:\zip)

Step 4: Copy the following files into this new directory:

  • classes.zip
  • javai.dll
  • zip.class
  • zip.exe

Step 5: Test the application

Change to the zip directory and type: zip. The following should appear on the screen:

c:\zip>zip
zip v1.0
usage: zip [-x file] zip

If you get to this point, then you've just executed your first dedicated Java/Win32 application. Congratulations!

Beyond ZIP

What comes next? The ZIP example illustrates a console user interface (CUI) Windows application, but Windows can also run graphical user interface (GUI) applications. A next step could be the creation of a standalone Windows GUI application written in Java. If you decide to go this route, however, you'll need some knowledge of various support DLLs that are distributed with the JVM.

The names of the following DLLs are pretty much self-explanatory. The Java Interpreter (or JVM) is stored in the javai.dll file. If you want to create a GUI-style application, you will also need to deploy the winawt.dll file. This file contains awt peer class support code as well as code to handle printing chores.

  • agent.dll
  • javai.dll
  • JdbcOdbc.dll
  • jpeg.dll
  • math.dll
  • mmedia.dll
  • net.dll
  • sysresource.dll
  • winawt.dll
  • zip.dll

If you get into the habit of creating lots of Java/Win32 applications, you may find that you are distributing multiple copies of classes.zip, one per application directory. At eight megabytes a pop, this gets rather expensive in terms of disk space usage. A better solution would be to modify the driver C++ file to obtain two separate paths: one from the initialization (ini) file (or even Windows registry) for the application classes, and another for classes.zip. You could then store classes.zip in a common directory.

A warning

Sun has made it clear through its JDK license agreements that all runtime files are to be distributed without modification. The ZIP example application required no changes to any runtime files, and it is doubtful that any application would need to have these files modified. However, this does not mean that all of these runtime files need to be distributed with any given application. I hope that Sun will relax this restriction.

Wrapping it up

When companies like Microsoft and Sun Microsystems release innovative products like Java and Win32, the computer industry benefits. I believe that the combination of these two technologies will usher in a new era of Windows software that is more robust than equivalent software written purely in C++. The ZIP application, introduced in this article, provides a bare glimpse of what is possible. I think this is exciting -- the potential is great. And who knows, maybe Sun and Microsoft will merge after all!

Jeff is a software engineer employed by EDS (Electronic Data Systems), a consulting firm founded by Ross Perot in the 1960s. He specializes in developing security applications for use with smart cards. He also writes his own Win32 and Java applications, which he sells as shareware.

Learn more about this topic

  • The zip archive contains all of the necessary C++/Java source and deployment files used by this article http://www.javaworld.com/jw-07-1998/java-win32/zip.zip
  • I've found the following resources beneficial when I'm writing applications that use the Java native Interface (JNI).
  • Essential JNI Java Native Interface, by Rob Gordon. PublisherPrentice Hall, Copyright1998. This book dives into JNI, and covers many techniques for calling C++ code with Java and calling Java with C++ code. One chapter focuses on the Invocation API and how to construct a C++ application which embeds the Java Virtual Machine -- in other words, how to create your own AppletViewer.exe program.
  • The JavaWorld article Use native methods to expand the Java environment provides an introductory look at the Java Native Interface http://www.javaworld.com/javaworld/jw-07-1997/jw-07-javadev.html