Caffeinator
An API for caffeinating your native application!
by Michael Fraenkel
Date: 4/25/97, Version: 1.1
Caffeinator
Caffeinator is a developer's tool that allows enables applications written
in C++ to create and manipulate Java objects within your native application.
The amount of code written in a native language such as C++ is far greater
than that of Java. To be able to extend your current code base to take
advantage of Java is a win-win situation. Caffeinator uses JNI to expose
Java classes as C++ classes.
An Example
Caffeinator is easiest explained first with an example. The example presented
here are excerpts from the sample code TestApp.cpp. To embed Java within
the example J-Empower is used. The example will show how to add a java.awt.event.MouseMotionListener
to a TestPanel.
Write the Java code for the panel called TestPanel.java.
import java.awt.*; public class TestPanel extends Panel {
Compile TestPanel.java so that we may generate a corresponding C++ class.
Start Caffeinator.
Add TestPanel as a class to generate.
Add java.awt.event.MouseMotionListener so that we may create a C++
event listener to override.
Double-click on TestPanel to display its PEMC.
Switch to the Events page. Notice that no events are present.
Change the end class to java.awt.Component.
Expose the mouseMotion event.
Generate the source code.
Save the session.
We will write some native code to embed the TestPanel and attach a mouse
motion listener to the panel. To embed the TestPanel into our native application,
we use some of the J-Empower functions. The following will create and embed
an instance of TestPanel into our application.
Create a new subclass of cafjava_awt_event_MouseMouseListener called MyMouseMotionListener
and override the methods we are interested in. For our example, we are
interested in all the methods.
class MyMouseMotionListener : public cafjava_awt_event_MouseMotionListener
{
public:
Next we will add the listener to our TestPanel using the registration method
addMouseMotionListener(). Since java.awt.event.MouseMotionListener is an
interface, we must create an interface instance of from the instance of
MyMouseMotionListener.
Compile all the java code that Caffeinator created, and build the application.
Installing and Running
To install Caffeinator, just place caffeinator.zip in your classpath.
To run Caffeinator, type java Caffeinator [<filename>]
<filename> is a Caffeinator project file
Classes and Interfaces
Caffeinator deals with Java classes and Java interfaces. The initial window
shows a hierarchical view of all the classes and
interfaces defined in the current Caffeinator session.
The list of classes is called the class list. As classes and interfaces
are added to Caffeinator, all superclasses are automatically added as well.
Interfaces that classes implement are not added automatically. These interfaces
must be added manually.
Classes and interfaces may be removed by hitting the DELETE key. If a class
or interface is a superclass of a class still defined, the class or interface
will not be removed. There are some classes like java.lang.Object, java.lang.String
and java.lang.Throwable which are considered permanent classes. These permanent
classes are necessary for the base code implementation and can never be
removed
Properties, Events, Methods, and Constructors
After selecting the initial set of classes and/or interfaces, double-clicking
on any of the listed classes/interfaces will display the properties, Events,
Methods, and Constructors panel (PEMC).
The PEMC will display all properties, events, and methods (features) for
a given class. The features displayed will depend on the "visiblity" selected.
The "visibility" is determined by the starting and ending class. All public
features found between the starting and ending class inclusively are displayed.
The starting class is always the class you are viewing and cannot be changed.
The ending class can be any class in the superclass hierarchy including
the starting class. By default the ending class is the starting class.
To change the ending class, select a new class from the drop-down listbox.
Each page displays a list of items on the left called all features and
a list on the right called exposed features. All exposed features are accessible
in the corresponding C++ class. To add exposed features, select all the
items from the all features list you wish to expose and hit the >>>> button.
To remove exposed features, select the items from the exposed features
list you wish to remove, and hit the <<<< button or hit the
DELETE key.
As properties, events, methods, and constructors are added, any classes
which they use either as a return value or a parameter are automatically
added to the class list. Classes however are never removed automatically
when the feature no longer exposed. Classes no longer in use must be removed
manually. If a feature is exposed, any classes it requires will not be
allowed to be removed from the class list.
Constructors will only be present for classes and not interfaces. The constructors
listed are only for the current class. Since constructors are not inherited,
changing the ending class will not affect this page.
Saving and Loading
Caffeinator has the ability to save and reload your work. To save your
current session, select the File menu from the Caffeinator window and select
Save... To reload any work that was previously saved, select the File menu
and select Load... When you start Caffeinator, you may also load a previous
session by passing the name of the file as a command-line argument.
Caffeinator can also import from a list of classes. The format of the file
is class name followed by methods to add, i.e., <class name> [all] [<method
name 1> <method name 2> ...]. The <class name> is the name of the
class that is to be add. To add all constructors and methods, specify "all".
When a method name is specified, all public methods matching the name are
added. For example, to expose the constructors of java.util.Hashtable and
all methods of java.util.Enumeration would look like:
java.util.Hashtable java.util.Hashtable
java.util.Enumeration all
Code Generation
Caffeinator will generate a C++ include file and a C++ source file for
every class or interface listed in the class list. Any interface that inherites
from java.util.EventListener listed will also have an extra C++ include
and C++ source file as well as a Java file.
The code generated for a class or non-java.util.EventListener interface
is simple to use. All parameters and return types correspond to either
their native Java type as is the case for all primitive Java types, e.g.,
byte, char, int, etc..., or their C++ equivalent. The C++ equivalent for
any Java class is the full name of the class including the package name
with the period (.) replaced with an underscore (_), e.g., java.lang.String
becomes java_lang_String.
An interface that has java.util.EventListener as a base class generates
three extra classes so that a programmer can create a C++ version of the
event listener.
Coding
Now that we have all the code, how do we use it? Let use first start by
looking at some of the helper files that are generated by Caffeinator.
The three helper files are JObject, JArray, and CPPXForm.
JObject represents the base class for all C++ classes corresponding to
a Java object. There are a few methods that are useful to programmers,
they are:
jclass getClass(); - Returns the object which represents the Class
of the object
jobject getObject(); - Returns the object
static JNIEnv* getJNIEnv(); - Returns the JNIEnv of the current thread
static void throwException(java_lang_Throwable &jobj); - Throws
an exception
A JObject is bound to either a local reference or a global reference. JObjects
that are bound to local references are only valid within the thread it
was created on. Global referenced JObjects can be used on any thread. However,
the JNIEnv created for a JObject is only valid for the thread it was create
on. Therefore, you should only use JObjects created within your thread.
If you are passing objects across threads, you must make a global referenced
JObject prior to copying it on another thread. For example, to pass a java.lang.String
that was created in thread 1 to thread 2:
JArray encapsulates array manipulation for both primitive types and objects.
The common methods for both types of array are:
jsize getLength(); - Returns the length of the array
<type> getValue(int i); - Returns the value at index i
void setValue(int i, <type> val); - Sets the value at index i to
val
For primitive types, there are two more methods for getting and setting
a consecutive group of values:
void getValues(jint start, jint len, <type> *buffer); - Get len
number of elements starting
at start and store them in buffer
void setValues(jint start, jint len, <type> *buffer); - Set len
number of elements starting at start from buffer
For example, to create an array of integers and set every element to its
index:
JIntArray intArray(5);
for(int i = 0; i < intArray.getLength(); i++)
intArray.setValue(i, i);
CPPXForm contains various transformations for strings. The current supported
transformations include:
jstring to/from string (C++)
jstring to/from wstring (C++)
jstring to java_lang_String
java_lang_String to/from string
java_lang_String to/from wstring
java_lang_String from char *
java_lang_String from wchar_t *
For example, to create a java.lang.String from a char * and then create
a string from that:
char *cstr = "This is a test";
java_lang_String jls = tojavalangString(cstr);
string cppString = toString(jls);
To transform an instance of a class to one of the interfaces it implements,
just create an instance of the interface passing the object of the instance.
For example, to create an instance of java.io.DataOutput from an instance
of java.io.DataOutputStream:
java_io_DataOutputStream dos = ...;
java_io_DataOutput do(dos.getObject());
Creating a C++ instance of an event listener is quite easy. For every event
listener, the C++ event listener class that must be overriden is given
the name of the C++ version of the Java class appended with caf, e.g.,
java.awt.event.ActionListener becomes cafjava_awt_event_ActionListener.
You must subclass this class and add whatever behavior you wish to those
methods you are interested in. To attach an instance of your event listener
just perform the same actions you would with any event listener by calling
the appropriate registration method. For example, to create an java.awt.event.ActionListener
and attach it to a button:
class myActionListener : public cafjava_awt_event_ActionListener
{
public:
After every method invocation or constructor invoked which creates a new
instance, a check for a thrown exception is performed. If a Java exception
was thrown, a corresponding C++ will be thrown. The only type of C++ exception
thrown is java_lang_Throwable. If you wish to know the specific exception
thrown, you can examine the class name of the thrown Java exception. For
example, if I put a null value into a java.util.Hashtable, a NullPointerException
is thrown:
Some future work will investigate the use of C++ namespaces to mimic
the package behavior of Java. Since most C++ compilers today do not support
namespaces, I chose to hold off on this feature.
Adding more transformations to CPPXForm for java.awt.Rectangle, java.awt.Dimension,
java.awt.Point, etc...
Other work will be to investigate how to simplify the local/global
referencing feature of JObject.