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.

Related:
1 2 Page 1
Page 1 of 2