Skip to main content

Using Java Classes in Windows Batch Files

April 30, 2004

{cs.r.title}








Contents
Motivation
Basics of the Command Line
Implementing Console-Based Programs in Java
Files and File Types
The TKJavaRunner Application
Some Examples
Limitations of TKJavaRunner
Conclusion
Resources

Although Java is an ideal language for implementing rich GUI
applications, it is equally well-suited for the development of small
console-based programs that, in turn, are predestined to be put together
in shell scripts or batch files. In this article, I will discuss how to
integrate Java classes and packages into Windows batch files.

Motivation

Although graphical user interfaces of today's operating systems provide elegant
means to control the machine and to initiate maintenance tasks, power
users and system administrators know and appreciate the benefits of the
command line. Small tools can be put together in so-called shell scripts
or batch files (the terminology varies among platforms, but all essentially
means the same thing) to achieve things that are impossible, or at least
impractical, with a GUI.

This is especially true for Linux, Mac OS X, and other Unix-like
operating systems, which provide particularly powerful command-line
interpreters (commonly referred to as shells). In principle, this is true
for Windows as well. From its beginnings, DOS had a component named
command.com and it is still present (in quite an enhanced
version, by the way) in current versions of Windows as
cmd.exe. Though it is far not as powerful as, for instance,
the Unix bash shell, it serves as a solid base for launching
maintenance tasks, administrating the machine, etc. Unix-like operating
systems are famous for their rich sets of command-line-centric tools.
Unfortunately, the number of such programs that are shipped with Windows
is much smaller.

But if a required tool is not at hand, why not implement it on your
own? That's how many of the above-mentioned Unix tools came into
existence, after all.

Basics of the Command Line

Before we get into Java, let us consider some basic techniques and
concepts: how do console-based programs work, in general? In Unix
environments, they used to be written in the C or C++ programming
language, and thus are native programs. To be easily accessible, they
are usually put in a common directory that is mentioned in the PATH
environment variable, so that the shell can find then.

Furthermore, these programs make use of a concept called channels, three of which are particularly important:
the standard input channel, the standard output channel, and the
standard error channel. Programs get input from the first, send output
to the second, and use the third to provide information if an error has
occurred. What physical media these channels are connected to is not
specified -- the program does not know, and doesn't need to know.
It may receive input from the console a user is sitting at the computer or from a
file. The same is true for output, which need not be a screen; it may be
a printer or a networked file.

Another important concept is that programs can be passed arguments that they can query as they start up. When they terminate, console-based programs can provide feedback if everything
went fine during execution. This is done using a so-called exit status,
which is typically zero if nothing unexpected happened.

All of the above traits are true for Windows, as well. Therefore, we can make use of these
concepts in console-based programs, which we in turn can tie together in
batch files. As I have already mentioned, in Unix-like environments
command-line tools usually reside in a few common directories that are
all mentioned in the PATH environment variable. This is necessary for
the shell to find them (when no absolute path is specified). This
applies to cmd.exe as well, so we should create a directory
in which we put all programs that we want to use inside of batch files.
Although the Windows directory is part of the search path
by default, I would advise you to not put your command-line tools there,
since that would be mixing system and user files. Instead, create your own bin folder in Program Files and extend your PATH to include that directory.

Implementing Console-Based Programs in Java

Fortunately, Java resembles C and C++ in many aspects. In particular,
Java programs meet all of the requirements of console-based applications.
To repeat, these are:

  • The ability to access the standard input, standard output, and standard error channels.
  • The ability to access the arguments that were passed upon startup.
  • The ability to provide a result (i.e., an exit code).

Taking this into account, Java seems a natural choice for console-based applications. There is one important drawback, though. Since the
standard Java compiler (javac) produces byte code, there
is no .exe file that we can put into our bin folder. The usual way to run a Java
application from the command-line prompt is to invoke the Java runtime,
passing it the name of the class containing the main method
or a .jar file (with a manifest file that mentions where main is) to
launch, plus additional arguments that will be passed to the program.
Though this is perfectly OK for program-launcher scripts, it is
inconvenient when writing shell scripts, since the preferred approach is
to simply write a program name and pass arguments to it. In our case,
this would be the class name. To solve this, we have two options:

  • Make them (or at least a part of them) native executables.
  • Make Windows think they are native.

Which approach is best for you depends on what you wish to do with your programs. There are
many tools available that can transform classes into native .exe files or put wrappers around them. For example, java.net recently featured an interesting series of articles by Joshua Marinacci about how to Make Your Swing App Go Native. In this second part of the series, Joshua introduced a commercial tool called JexePack that creates
native executables with unique program icons. Such wrapper programs actually launch the Java interpreter,
which in turn runs the Java program. This is certainly great if your programs have to look professional; for instance, because you plan to distribute them. So theoretically, we might use this tool for our purposes as well, but
this implies that for each Java class we plan to use in a batch file, we
need to create an appropriate native wrapper.

Another (at least theoretical) possibility is to use the GNU Compiler for the Java Programming Language, which can create native binaries. However, installing and using the GNU Compiler Collection on a Windows-based machine is far beyond the scope of this article. So let us analyze the second approach.

Files and File Types

What we are trying to do is make Windows think that .class files are (or at
least behave like) native executables. If you double click on a .html file, your browser is launched and displays the file. The same is true for sound files, graphics files, and so on: files of a certain type can be assigned an application that is used to render, display, or otherwise present the contents of the file. Opening those files works within cmd.exe, too. To try, launch cmd.exe and navigate to a directory that contains a .html file, for example. Then type the name of that file including its extension, and hit Enter. The browser will show your file. If we assign .class files to the Java runtime system (java.exe) and put the class files in our bin folder (the one I suggested creating above, so you can put your class files there and not mix them up with system files), we can access them from batch files by writing <class>.class. At the moment, we cannot omit the extension, because this is allowed for executables only. We will see how to fix that later. We do have a greater problem, though.

java.exe does not expect filenames, but class names. If you double-click on a file, its name and path are passed to the application. Sadly, this is not what java.exe needs. However, an application that takes the filename of the class to launch, extracts the classname, and passes that to java.exe should do the trick.







The TKJavaRunner Application

What would such a class launcher look like? Below we have a trivial
implementation called TKJavaRunner. For your convenience, I have provided
an archive with all listings, class files, and executables in the Resources section below. To compile TKJavaRunner.exe I used the excellent
lcc-win32 IDE and compiler package.

/*********************************
*                               *
* TKJavaRunner                  *
*                               *
* This program invokes the Java *
* runtime with a class name     *
* which is built from the first *
* argument; further arguments   *
* are passed to the Java        *
* application                   *
*                               *
*********************************/

#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define MAXLEN 256

int main(int argc, char **argv) {
   char *path;
   char *classname;
   char *extension;
   int result = 0;
   char cmd[MAXLEN];
   int i, len;

   if (argc < 2) {
      fprintf(stderr,
        "TKJavaRunner [.class file} [argument1] [argument...]\n");
      result = 1;
   } else {
      path = argv[1];

      classname = strrchr(path, '\\');
      if (classname == NULL)
         classname = path;
      else
         *(classname++) = 0;

      extension = strrchr(classname, '.');
      if (extension == NULL)
         result = 1;
      else {
         *(extension++) = 0;

         sprintf(cmd, "java -cp "%s" %s",
                 path, classname);
         len = strlen(cmd);
         for (i = 2; i < argc; i++) {
            len += (strlen(argv[i]) + 1);
            if (len >= MAXLEN)
               break;

            strcat(cmd, " ");
            strcat(cmd, argv[i]);
         }

         // fprintf(stderr, "%s\n", cmd);

         result = system(cmd);
      }
   }
   return result;
}

Let us have a look at the program. It expects at least one argument,
the complete path and name of the class file to launch. Additional
arguments will be passed to the class upon startup of the Java virtual
machine. Since java.exe expects a class name, we remove the path and the
extension. Then a new command line is constructed that contains the name
of the Java runtime launcher (java.exe) with several arguments:

  • The -cp option to add the directory that contains our class file to the class path.
  • The class name without its extension.
  • Any additional arguments.

The program assumes that Windows knows where to find java.exe; its
directory therefore has to be mentioned in PATH. If you work with Java at
the command-line prompt now and then, this is quite a good idea anyway.
But how do we use TKJavaRunner? We need to assign .class files to it.
Though the specifics may vary depending on your version of Windows, the basic
steps are as follows. First, open the Folder Options dialog, as shown in Figure 1.

Figure 1
Figure 1. Opening the Folder Options dialog

After that, check to see if .class is already a registered file type.
If this is the case, you will find an entry similar to the one shown in
Figure 2. If not, create an entry by selecting the New button.

Figure 2
Figure 2. Browsing the list of known file types

Once you have made sure .class is a registered file type, hit the
Advanced button to check the settings. Please note that in Figure 3 we
pass up to nine arguments to the program. Since %1 refers to the class
file itself, we need to make sure that additional arguments are passed to
our program. TKJavaRunner is a simple console-based program;
therefore, the other settings in this dialog box do not apply.

Figure 3
Figure 3. Configuring the .class association

There is one more problem we need to fix. Since we want our classes
to behave as native executables, we want to be able to launch them
without their .class extensions. Windows maintains a list of
extensions to be treated as executables. To modify this list,
right-click your My Computer icon on the desktop and select Properties from the contextual
menu. A dialog box will appear. Move to the Advanced tab. You
will notice a button labelled Environment. If you select that, another
dialog box will pop up. As you can see in Figure 4, there is a variable
called PATHEXT, which contains a semicolon-delimited list of extensions
of executable files. We just need to add .class to this list. After that
we can run our classes just like native executables.

Figure 4
Figure 4. Modifying the PATHEXT variable

Some Examples

Now that we have configured Windows to recognize .class files, let us do
some testing with cmd.exe. I have written two sample programs,
named FileSize and Sum. You can get the source and class files in the Resources
section at the bottom of this article. Put
at least the class files to your previously created bin folder,
so that cmd.exe can find them.

FileSize expects filenames as its arguments. For each given filename, it prints the size of the
file to the standard output. Sum does not take arguments, but reads
lines from standard input. Each line is treated as a decimal number.
Sum sums all numbers it has read up until an end of file signal is received.
It then prints the result to standard output.

Neither of these programs is particularly useful on its own, but if we combine them, we
can, for example, compute the size of a certain directory. Within cmd.exe type FileSize * > sizes.txt and hit Enter. You can check the result by executing type sizes.txt. After that, type Sum < sizes.txt. What happens here is that cmd.exe expands * to a list of all file (and directory) names in the current directory. These names are passed to
FileSize, which prints the sizes of the files. Since we redirected the
standard output of FileSize, we did not see anything on screen. The values were
written to a file named sizes.txt instead. With Sum we do the opposite; we
force it not to read from the keyboard but from the previously created file.
We could write a small batch file that takes one argument, the pathname of a
directory, and computes the sum of all file sizes within this directory.

Limitations of TKJavaRunner

So far, TKJavaRunner seems to do a good job. However, it has some
significant drawbacks, which I am going to cover in this section.
The first one is obvious: it does not handle .jar files. This is a minor
issue, since .jar files usually contain more complex, GUI-based applications.
On the other hand, the Java runtime usually registers .jar
as a file type, so if this .jar archive contains a manifest file that
specifies the class containing the main method, it can be launched with a
double click. And if we add .jar to the PATHEXT environment
variable, we can run the archive from cmd.exe as well.

Another minor drawback is that Windows launches TKJavaRunner,
which in turn starts the Java runtime. We might consider this a
performance issue, since it takes place each time a Java class has to
be launched (from cmd.exe). My assumption is, however, that we can ignore the
time needed to launch TKJavaRunner, especially when compared to
the time needed to bring up the Java runtime.

The biggest drawback is that TKJavaRunner cannot handle classes that contain a
package statement. In order to find packaged classes, the virtual
machine must know the base directory of the package, not the directory that
contains the class. But that is exactly what TKJavaRunner does. It determines
the path of the class and adds that directory to the classpath. To illustrate this, imagine you have put Sum.class into C:\bin. If you run it from cmd.exe,
C:\bin\Sum.class is passed to TKJavaRunner, which sets the classpath to C:\bin using the -cp option and determines Sum as the class name. Since Sum has no package statement, TKJavaRunner is right with its assumption. However, if we assign it to the package com.oreilly.tkuenneth, the class file should be in a directory
named some path\com\oreilly\kuenneth. To work correctly,
TKJavaRunner would need to add some path to the classpath
and determine com.oreilly.tkuenneth.Sum as the (fully
qualified) class name, which it, as we have already seen, does not.

The fact that packages produce a directory tree presents another problem.
Remember that cmd.exe finds class files because we added a
bin folder to PATH. To find a class nested deep inside of a
package hierarchy, we would need to add each directory that is part of this
tree to PATH. This is not practical, for the following reasons:

  • PATH becomes difficult to maintain.
  • Each time a filename has to be resolved, all PATH entries are scanned. To a certain extent, this might affect the performance of your machine.

Consequently, it seems reasonable not to package the main
class of programs you wish to access from the command line.
Of course, they may reference classes of other packages
if the Java runtime knows the path of the package base
directory. This, for example, applies to Java extensions,
which are put in the ext directory.
This is particularly convenient, since you can put
any .jar file there. Another option is to modify TKJavaRunner
to add another directory to the classpath using the -cp
option.

Conclusion

Java provides everything necessary to write console-based programs.
Since class files contain byte code, they cannot be invoked directly.
Instead, the Java runtime has to be launched first. To mimic the
behavior of native executables in Windows, we have used file associations
and an appropriate wrapper program. This allows us to treat class files
almost as .exe files. As I have mentioned earlier,
the shell plays a very important role in Unix-like environments.
Therefore it seems desirable to use Java classes in shell scripts
just like native executables in these environments, too.
Unfortunately shells do not know file associations, so there is no
common approach we could use for all Unix-like operating systems.
For Linux, there is a kernel module called binfmt_misc,
which adds support for the execution of non-native binary formats, which is based on a magic number or a file extension. This mechanism is similar to our approach using a wrapper
program. A file that contains a magic number or has an appropriate extension
is passed to its corresponding launcher program. I hope to discuss this Linux feature in a later article.

In Windows, the approach of using a launcher program does not
necessarily require a native program. What we could do instead is
assign .class files to java.exe with
a command line as follows:

java.exe TKClassLauncher 
"%1" "%2" ... "%9"
. The TKClassLauncher
class would act as our launcher program, so it determines the class
to be started, loads it, and invokes its main method.
Doing so has several advantages:

  • We need no extra time to launch a native wrapper program; instead, the Java runtime is brought up immediately.
  • We could even solve the problem regarding packages by implementing an appropriate ClassLoader that does not depend upon a previously defined classpath.

Since dealing with the ClassLoader class is an interesting topic,
it might be worth a further article. In any case, I greatly
appreciate your feedback.

Resources

Source code and binaries for TKJavaRunner, FileSize, and Sum

Thomas Kunneth works as a software architect at the German authorities, specializing in Java-based rich clients.
Related Topics >> Programming   |