JNI++ Generator: Input, Java Proxies, JNI Stubs
last revision: Tue Jun 13 17:32:27 2000
1. OverviewThis paper describes in short the generator tool of the JNI++ open-source project. Since the JNI++ is, at present, not much more than an idea and vision, it takes the only thing already materialized: a simple but working example of couple of native code classes subclassed by a Java subclass. This paper describes the code patterns to be generated by the JNI++ generator tool to connect the native C++ superclasses with the Java subclass to arrange for both:
The envisioned JNI++ generator should generate all the code necessary for linking a newly created Java subclass to the given native superclasses. 2. JNI++ Generator's InputThe most important input to the JNI++ generator are the definition files for native C++ classes. The sample class definitions are shown here. At the moment, it is yet unclear if all necessary information can be derived from the C++ header files. Furthermore, parsing C++ headers is quite a hard job todo and there is no generic C++ parser around, which would fit with JNI++. That's why, for the moment, a simple generator written in Perl is used to generate all the JNI++ code from some handmade interface description language. This generator will be described in a future paper.3. Type Mapping InformationSince Java types and C++ types differ, some kind of type mapping is required. This topic needs further investigation.4. Generator OutputAs turns out to be, the JNI++ generator has to produce 4 different kinds of output for each native C++ class, namely
4.1. Generated Java Proxy ClassesIn order to make the native superclasses appear and behave like Java classes, a Java proxy class is generated for each native C++ class in question. It should be no supprise that these proxy classes contain a JNI interface.And this is how the example's proxy classes look like:
class Foo : Java proxy
package FooBar;
/*
Summary: Java proxy for C++ class Foo
Purpose:
This is the JNI++ Java proxy for the native C++ class Foo.
A Java proxy for a C++ class is a Java class which declares a native
Java function for each C++ member function exported by the C++ class.
Furthermore it does some book keeping stuff in order to make JNI++ work.
Todo:
DocJet does not recognize Java native functions yet.
Note:
Do not touch this file! It was entirely generated by Metaphor
at Tue Jun 13 17:17:18 2000 from its IDL source.
*/
public class Foo
extends Bar
{
/*
Summary: Constructor with same signature as native C++ constructor
Parameters:
message - see Foo::Foo() for doc
Purpose:
Use this constructor to create direct instances of class Foo.
Consult doc of native C++ class for a description of the purpose
constructor and meaning of the parameters.
*/
public Foo( String message )
{
super( 0 );
create( jni_create( message ) );
}
/*
Summary: Create the native C++ part of an instance of class Foo
Returns: the handle to the C++ instance
Purpose:
This native function creates the C++ part of an instance of Java class
Foo.
*/
private native int jni_create( String message );
Foo( int nativeSelf )
{
super( nativeSelf );
}
public native Bar foo( String message, Bar object );
public native Bar getBar( );
public native int passCount( int count );
}
First of all, since the native class Foo is a subclass of native class
Bar, the Java proxy Foo is a subclass of Java proxy Bar.
Furthermore, for each member function of the native class Foo there is a Java native method declaration with corresponding signature (the problems of Java/C++ type conversion are discussed later). Then, there is a constructor with a signature corresponding to the native class Foo's constructor. This constructor is called for the creation of direct instances of Java Foo. It is implemented via a native function jni_create() with the constructor's signature. This JNI function serves for creation of the C++ wrapper which constitutes the native C++ part of the newly created instance (explained later). But before this can be done, the superclass is to be constructed. Each Java proxy provides an additional constructor with a single integer as argument. This constructor is called the proxy constructor. It is used in Java proxy classes extending another Java proxy class. Foo itself provides such a constructor, too. And Foo calls Bar's proxy constructor before creating the native part. The role of the proxy constructor's argument and the create() function are explained later. The code for the Java Foo proxy is fairly straight forward and can be
derived easily from the native C++ class definition: The implementation
of the proxy constructor uses always the above simple pattern. For each
native C++ constructor there is a Java constructor and a native jni_create()
function with corresponding signature. For each member function declared
in the native C++ class a Java JNI native function declaration is generated.
That's all.
class Bar : Java proxy
package FooBar;
/*
Summary: Java proxy for C++ class Bar
Purpose:
This is the JNI++ Java proxy for the native C++ class Bar.
A Java proxy for a C++ class is a Java class which declares a native
Java function for each C++ member function exported by the C++ class.
Furthermore it does some book keeping stuff in order to make JNI++ work.
Todo:
DocJet does not recognize Java native functions yet.
Note:
Do not touch this file! It was entirely generated by Metaphor
at Tue Jun 13 17:17:19 2000 from its IDL source.
*/
public class Bar
extends Base
{
/*
Summary: Constructor with same signature as native C++ constructor
Parameters:
message - see Bar::Bar() for doc
Purpose:
Use this constructor to create direct instances of class Bar.
Consult doc of native C++ class for a description of the purpose
constructor and meaning of the parameters.
*/
public Bar( String message )
{
super( 0 );
create( jni_create( message ) );
}
/*
Summary: Create the native C++ part of an instance of class Bar
Returns: the handle to the C++ instance
Purpose:
This native function creates the C++ part of an instance of Java class
Bar.
*/
private native int jni_create( String message );
Bar( int nativeSelf )
{
super( nativeSelf );
}
public native Bar bar( String message, Bar object );
public native Bar foo( String message, Bar object );
public native String getMessage( );
}
The code for the Java proxy Bar differs only in its superclass. The native
C++ class Bar has no superclass. Java proxy Bar extends Base (see below).
All the other code should be already familiar: There is the Java constructor
corresponding to the native C++ constructor together with its native jni_create()
function. Then there is the proxy constructor. Finally, there is a Java
native JNI function for each member function declared in the native C++
class.
A few words about Base: This class is part of JNI++ and provides some services useful for all JNI++ Java proxies, namely some bookeeping for JNI++ and the finalize() function which is an equivalent of native C++ destructors. All destructors have a common implementation in JNI++. Base is explained in detail later. The generation of proxy classes from C++ class definitions following the above patterns seems to be straight forward. All necessary information can be derived by parsing the class definition. There is a one-to-one correspondence between the native C++ members and the Java proxy members. Only a handful of JNI++ specific functions is to be created. Note that private members of the C++ classes are not mirrored in Java. Handling of non-private member attributes is discussed later. Once these proxies are generated, Java subclasses can be derived easily and in genuine Java style. Native C++ code does not show up in Java subclasses. Subclass programmers need not care about C++, provided the accessed JNI code works correctly. 4.2. Generated JNI Stub CodeNext the JNI code is to be generated. At this point it is important to meet the JNI conventions in full extent.4.2.1. The JNI Stub Function DefinitionsJNI itself comes with the javah tool to generate C stub function declarations for Java classes containing native functions. Whether javah is used as is or a module of the JNI++ generator produces similar code can left be open for the moment. Beside the header files, JNI++ generates the stub implementations, too. These are the C++ stub headers generated for the example's Java proxy classes by javah:class Bar : JNI stub definition
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class FooBar_Bar */
#ifndef _Included_FooBar_Bar
#define _Included_FooBar_Bar
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: FooBar_Bar
* Method: bar
* Signature: (Ljava/lang/String;LFooBar/Bar;)LFooBar/Bar;
*/
JNIEXPORT jobject JNICALL Java_FooBar_Bar_bar
(JNIEnv *, jobject, jstring, jobject);
/*
* Class: FooBar_Bar
* Method: foo
* Signature: (Ljava/lang/String;LFooBar/Bar;)LFooBar/Bar;
*/
JNIEXPORT jobject JNICALL Java_FooBar_Bar_foo
(JNIEnv *, jobject, jstring, jobject);
/*
* Class: FooBar_Bar
* Method: getMessage
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_FooBar_Bar_getMessage
(JNIEnv *, jobject);
/*
* Class: FooBar_Bar
* Method: jni_create
* Signature: (Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_FooBar_Bar_jni_1create
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
class Foo : JNI stub definition
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class FooBar_Foo */
#ifndef _Included_FooBar_Foo
#define _Included_FooBar_Foo
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: FooBar_Foo
* Method: foo
* Signature: (Ljava/lang/String;LFooBar/Bar;)LFooBar/Bar;
*/
JNIEXPORT jobject JNICALL Java_FooBar_Foo_foo
(JNIEnv *, jobject, jstring, jobject);
/*
* Class: FooBar_Foo
* Method: getBar
* Signature: ()LFooBar/Bar;
*/
JNIEXPORT jobject JNICALL Java_FooBar_Foo_getBar
(JNIEnv *, jobject);
/*
* Class: FooBar_Foo
* Method: jni_create
* Signature: (Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_FooBar_Foo_jni_1create
(JNIEnv *, jobject, jstring);
/*
* Class: FooBar_Foo
* Method: passCount
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_FooBar_Foo_passCount
(JNIEnv *, jobject, jint);
#ifdef __cplusplus
}
#endif
#endif
This is pure JNI stub code which needs no further comment. The more interesting point is the implementation of these functions, described below. 4.2.2. Calls of C++ Members from JavaAn instance of a Java subclass extending a native C++ class consists of a Java part and a C++ part. The Java proxy serves for setting up the Java part. Now let's care for the C++ part.First for construction. Whenever a Java instance is created, the appropriate constructor of the Java instance calls the jni_create() function with matching signature. This function is responsible for creating the C++ part of the instance. As long as no virtual C++ function is to be overridden, it suffices to create and attach an instance of the respective C++ superclass (called the native self) to the already constructed Java object and to use the native self in the JNI stubs for the native C++ member functions. An implementation without virtual function callbacks could look like
this:
class Foo: JNI stub create sample
JNIEXPORT jint JNICALL Java_Foo_jni_1create(JNIEnv * env, jobject self, jstring message )
{
jniString jni_messageString( env, message );
const char * jni_message = jni_messageString.jni_getNative();
jniFooBase * nativeSelf = new jniFooJava( env, self, jni_message );
return (jint) dynamic_cast<jniBase *>(nativeSelf);
};
Beside type conversion, the jni_create() function simply creates an instance
of the respective C++ class and returns a handle to the newly created C++
object back to Java. Looking back at the constructor in the Java proxy,
one can see that this handle is passed to the function create() implemented
in Base. So, it's time to have a closer look at Base:
class Base : definition
package FooBar;
/*
Summary: ultimate Java base class for Java JNI proxies
Purpose:
This is the base class for all (generated) Java proxies. There is one Java
proxy for each native C++ class exported by the native library. The Java class
base provides technical support for the Java/C++ connection accross JNI++.
Todo:
This class provides the native function jni_destroy(int nativeSelf) to destroy
the C++ part of the instance. DocJet does not recognize native functions yet.
*/
class Base
{
/*
Summary: the handle to the C++ instance
Purpose:
JNI++ is aimed at subclassing C++ by Java. For this purpose, each Java
instance has a handle to its C++ part (actually a pointer to a C++ object).
This member stores this handle on behalf of the JNI mechanism.
*/
private int jni_NativeSelf;
/*
Summary: create base part of the Java instance
Parameters:
nativeSelf - the native self, a handle to the C++ part of the instance
Purpose:
Stores the handle to the C++ instance.
*/
public Base( int nativeSelf )
{
create( nativeSelf );
}
/*
Summary: stores the handle to the C++ instance
Parameters:
nativeSelf - the native self, a handle to the C++ part of the instance
Purpose:
Call this method in Java proxies to store
the native self, which is in fact a handle
to the underlying native adapter object.
This function does the dirty job for the
constructor but can be called by subclasses
directly, too.
*/
protected void create( int nativeSelf )
{
jni_NativeSelf = nativeSelf;
}
/*
Summary: destroy the Java instance
Purpose:
This function is called (during garbage collection
or manually) in order to get rid of the underlying
adapter objects.
*/
protected void finalize()
{
jni_destroy( jni_NativeSelf );
}
/*
Summary: destroy the C++ instance
Purpose:
This native function does the dirty job for finalize(),
i.e. it destructs the native adapter object identified
by native self.
*/
private native void jni_destroy( int nativeSelf );
}
There is no mistery about Base: It stores the native self (the handle
to the C++ part) handed over by the create() function in a private variable
named jni_NativeSelf. As usual in C++, the destruction of dynamically constructed
C++ objects must be done explicitely. This holds for the C++ object behind
native self, too. The finalize() function of Base serves for this purpose:
it calls the JNI native function jni_destroy() with the native self handle
as argument, which, in turn, deletes the C++ object.
With this bookeeping background, a JNI stub for a native member function
(without virtual callbacks) could then look like this:
class Bar: JNI stub member function sample
JNIEXPORT jint JNICALL Java_Foo_getCount(JNIEnv* env, jobject self, jint count )
{
int jni_count = count;
jniFooBase * nativeSelf = dynamic_cast<jniFooBase *>(
reinterpret_cast<jniBase *>( jniBase::jni_getNativeSelf( env, self ) ) );
int reply = reinterpret_cast<Foo *>(
nativeSelf->jni_getNative())->Foo::getCount( jni_count );
return reply;
}
The JNI stub first inquires the native self handle stored in the Java part
of the instance. Since JNI allwos the access even to private members, it's
easy to get the native self stored in the Java attribute jni_NativeSelf.
Then the member function of the C++ object is called, it's return value
is converted from C++ to Java, and the result is passed back to Java.
This is one of the implementations usually found for accessing C++ member functions from out of Java. It's easy, it's straight, it works. But it fails to handle calls to virtual functions from inside C++ code but overridden in Java subclasses. 4.2.3. Callback Virtual C++ Functions Overridden by Java SubclassesWhen a virtual function is called, this call is directed to the farest subclass providing an override with matching signature to that function. In the mixed language case, this override of a C++ virtual function might be provided by a Java subclass. Special measures must be taken to emulate this situation across language boundaries.JNI++ takes the following approach: An C++/Java wrapper class is generated for each native C++ class in question. This wrapper class is a C++ class which
class Bar: Java wrapper definition
#ifndef jniBarJava_h
#define jniBarJava_h
#include "jniBarBase.h"
#include "Bar.h"
class Bar;
/*
{group:wrapper}
Summary: Java wrapper for C++ class Bar
Purpose:
This is the JNI++ Java wrapper for the native C++ class Bar.
Java wrappers are used whenever a Java subclass is derived from a C++
superclass using JNI++. A Java wrapper for a C++ class is a C++ subclass
of this C++ class. It constitutes the C++ part of a Java instance of
the Java subclass. It declares a wrapper function for each C++ virtual function
provided. It organizes the callbacks from C++ back to Java via virtual functions.
Note:
Do not touch this file! It was entirely generated by Metaphor
at Tue Jun 13 17:17:19 2000 from its IDL source.
*/
class jniBarJava
: public jniBarBase
, public Bar
{
public:
/*
Summary: Constructor with same signature as native C++ constructor
Parameters:
env - the JNI environment for that call
self - the Java self
message - see Bar:: for doc
Purpose:
Use this constructor to create direct instances of class Bar.
Consult doc of native C++ class for a description of the purpose
constructor and meaning of the parameters.
Parameters:
env - the JNI environment for the object
self - the JNI handle for the Java object
*/
jniBarJava( JNIEnv * env, jobject self, const char * message );
virtual ~jniBarJava();
/*
Summary: Override of native C++ virtual function
Returns: see Bar::bar for doc
Parameters:
env - the JNI environment for that call
self - the Java self
message - see Bar::bar for doc
object - see Bar::bar for doc
Purpose:
The JNI++ Java wrapper class provides an override for each virtual function
provided by the native C++ class in question. Such an override serves for calling
the appropriate Java function in the Java part of the instance whenever the virtual
function is called in the C++ part. This is on the very heard of JNI++.
*/
virtual Bar * bar( const char * message, Bar * object );
/*
Summary: Get the native self
Returns: The native self, a handle to the C++ part of the instance (the this pointer)
Purpose:
This is the Java wrapper's implementation of the interface function declared in
jniBarBase. JNI++ uses this function to inquire the native self, a pointer to
the C++ part of the instance.
*/
virtual Bar * jni_getNative();
/*
Summary: Destroy the C++ part of the instance
Purpose:
This is the Java wrapper's implementation of the interface function declared in
class jniRoot. JNI++ uses this function to destroy the C++ part of the instance.
*/
virtual void jni_destroy();
};
#endif
The constructor takes the JNI environment and a handle to the Java part
of the instance, the Java self.
With these values, a link to the Java proxy can be established via JNI during
virtual function callbacks.
The override of the virtual function bar() invokes the Java proxy's function bar() with corresponding signature and thus of any Java subclass overriding this function. Finally, the return value must be converted from Java to C++ and passed back to the native C++ caller.
class Bar : JNI stub implementation
#include "jniext.h"
#include "Bar.h"
#include "jniBar.h"
#include "jniBarJava.h"
#include "jniBarNative.h"
#include "jniString.h"
#include "jniBarNative.h"
/*
{group:JNIstub}
Summary: create an instance of native C++ class Bar
Returns: the native self, a handle to the C++ instance
Parameters:
env - the JNI environment for that call
self - the Java self
message - see constructor of Bar for doc
Purpose:
This function generates the C++ part of an Instance created by the
JNI++ Java proxy Bar
Note:
Do not touch this function! It was entirely generated by Metaphor
at Tue Jun 13 17:17:19 2000 from its IDL source.
*/
JNIEXPORT jint JNICALL Java_FooBar_Bar_jni_1create(JNIEnv * env, jobject self
, jstring message )
{
jniString jni_messageString( env, message );
const char * jni_message = jni_messageString.jni_getNative();
jniBarBase * nativeSelf = new jniBarJava( env, self, jni_message );
return (jint) dynamic_cast<jniRoot *>(nativeSelf);
};
/*
{group:JNIstub}
Summary: JNI stub implementation for virtual function
Returns: see Bar::bar for doc
Parameters:
env - the JNI environment for that call
self - the Java self
message - see Bar::bar for doc
object - see Bar::bar for doc
Purpose:
This function propagates the function call from Java proxy Bar to
native C++ Bar::bar and does the appropriate type
conversions for parameters and return values.
Note:
Do not touch this function! It was entirely generated by Metaphor
at Tue Jun 13 17:17:19 2000 from its IDL source.
*/
JNIEXPORT jobject JNICALL Java_FooBar_Bar_bar(JNIEnv* env, jobject self
, jstring message, jobject object )
{
jniString jni_messageString( env, message );
const char * jni_message = jni_messageString.jni_getNative();
Bar * jni_object = dynamic_cast<Bar *>(
dynamic_cast<jniBarBase *>( reinterpret_cast<jniRoot *>(
jniRoot::jni_getNativeSelf( env, object ) ) )->jni_getNative());
jniBarBase * nativeSelf = dynamic_cast<jniBarBase *>(
reinterpret_cast<jniRoot *>( jniRoot::jni_getNativeSelf( env, self ) ) );
Bar * reply = reinterpret_cast<Bar *>(
nativeSelf->jni_getNative())->Bar::bar( jni_message, jni_object );
jniBarBase * replyBase = jniBarBase::FindJNIBase( reply );
if( replyBase == 0 )
replyBase = new jniBarNative( env, reply );
return replyBase->jni_getJava();
}
/*
{group:JNIstub}
Summary: JNI stub implementation for non-virtual function
Returns: see Bar::foo for doc
Parameters:
env - the JNI environment for that call
self - the Java self
message - see Bar::foo for doc
object - see Bar::foo for doc
Purpose:
This function propagates the function call to Bar::foo and
does the appropriate type conversion:
Note:
Do not touch this function! It was entirely generated by Metaphor
at Tue Jun 13 17:17:19 2000 from its IDL source.
*/
JNIEXPORT jobject JNICALL Java_FooBar_Bar_foo(JNIEnv* env, jobject self
, jstring message, jobject object )
{
jniString jni_messageString( env, message );
const char * jni_message = jni_messageString.jni_getNative();
Bar * jni_object = dynamic_cast<Bar *>(
dynamic_cast<jniBarBase *>( reinterpret_cast<jniRoot *>(
jniRoot::jni_getNativeSelf( env, object ) ) )->jni_getNative());
jniBarBase * nativeSelf = dynamic_cast<jniBarBase *>(
reinterpret_cast<jniRoot *>( jniRoot::jni_getNativeSelf( env, self ) ) );
Bar * reply = reinterpret_cast<Bar *>(
nativeSelf->jni_getNative())->Bar::foo( jni_message, jni_object );
jniBarBase * replyBase = jniBarBase::FindJNIBase( reply );
if( replyBase == 0 )
replyBase = new jniBarNative( env, reply );
return replyBase->jni_getJava();
}
/*
{group:JNIstub}
Summary: JNI stub implementation for non-virtual function
Returns: see Bar::getMessage for doc
Parameters:
env - the JNI environment for that call
self - the Java self
Purpose:
This function propagates the function call to Bar::getMessage and
does the appropriate type conversion:
Note:
Do not touch this function! It was entirely generated by Metaphor
at Tue Jun 13 17:17:19 2000 from its IDL source.
*/
JNIEXPORT jstring JNICALL Java_FooBar_Bar_getMessage(JNIEnv* env, jobject self
)
{
jniBarBase * nativeSelf = dynamic_cast<jniBarBase *>(
reinterpret_cast<jniRoot *>( jniRoot::jni_getNativeSelf( env, self ) ) );
const char * reply = reinterpret_cast<Bar *>(
nativeSelf->jni_getNative())->Bar::getMessage( );
return env->NewStringUTF( reply );
}
Instead of creating a direct instance of the C++ class in question, the
jni_create Function creates an instance of the Java wrapper class.
That's all.
Although this is a simplified version, the JNI functions for the class members look rather tricky. But it's not hand coded! It's generated by the JNI++ generator. The main idea is to inquire the handle to the C++ wrapper object created by jni_create() and to call the respective method of the native C++ class. The point is, that the Java wrapper's possible override is explicitely skipped: The override in the Java wrapper class is for callbacks only, not for calls from a subclass. The methods of the original C++ classes are called directely. Both, Bar::foo() and Bar::bar() return a pointer to a C++ instance of class Bar. In order to give the calling Java program access to such an instance, it must be wrapped by JNI++, if not already done. The FindJJNIBase() call on the reply checks this. In case the reply is not wrapped yet, replyBase gets NULL and the JNI stub creates a Java/C++ wrapper for the returned instance. This is covered by JNI++ part 3. |
|||||||||||||||||||||||||