JNI++ Generator: JNI++ Wrapper Classes

Klaus Wiederänders

previous  home  next 

last revision: Tue Jun 13 17:32:28 2000

1. Overview

As turns out to be, the JNI++ generator has to produce 4 different kinds of output for each native C++ class, namely
  • a Java proxy class defining native functions
  • the JNI stubs for each native function defined in the Java proxy
  • a Java wrapper class for callbacks (overridden virtual functions)
  • a C++ wrapper class for C++ instances passed to Java functions.
Java proxy classes and JNI stubs were explained in in JNI++ part 2. Wrapper classes are described below.

2. Generated JNI++ Wrapper Classes

The JNI++ generator generates two kinds of wrapper classes:
Java wrappers
For each native C++ class to be subclassed by Java, a Java wrapper is generated. This kind of wrapper serves for the emulation of virtual function overrides across language boundaries: Whenever an overridden virtual function is called in some C++ superclass, this call must be directed to the respective function of the farest subclass which, in the JNI++ case, is a Java class. For this purpose, the Java wrapper is itself a C++ subclass of the original native C++ class.
C++ wrappers
For each native C++ class with instances passed (as result or parameter) to Java, a C++ wrapper is generated. Instances of this class are used to manage access to the C++ instance from Java, if no Java proxy and thus Java wrapper yet exists. This kind of wrapper serves calls to the managed instance's member functions from out of Java throug an interface common to both wrapper versions.

2.1. Java Wrapper Classes

When 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: A Java wrapper class is generated for each native C++ class in question. This wrapper class is a C++ class which

  • is a subclass of the original native C++ class
  • overrides all virtual functions provided by the original C++ class
The Java wrapper class catches calls of virtual functions and transforms these calls into Java proxy calls. Since all Java functions are virtual, the Java proxy calls end up in calls to methods of Java subclasses overriding the Java proxy methods. This is the basic idea.

class Bar : Java wrapper implementation

#include "jniBarJava.h"

#include "jniBarNative.h"
#include "Bar.h"


jniBarJava::jniBarJava( JNIEnv * env, jobject self, const char * message )
: jniBarBase()
, Bar( message )
{
    jni_setEnv( env );
    jni_setJava( self );
}
    
jniBarJava::~jniBarJava()
{
}

Bar * jniBarJava::bar( const char * message, Bar * object )
{
    if( JDEBUG ) printf("%s(%d) jniBarJava::bar(%p)\n", __FILE__, __LINE__, this);
    jstring jni_message = jni_getEnv()->NewStringUTF( message );
    if( JDEBUG ) printf("%s(%d) jniBarJava::bar(%p)\n", __FILE__, __LINE__, this);
    jniBarBase * jni_objectBase = jniBarBase::FindJNIBase( object );
    if( JDEBUG ) printf("%s(%d) jniBarJava::bar(%p)\n", __FILE__, __LINE__, this);
    if( jni_objectBase == 0 )
        jni_objectBase = new jniBarNative( jni_getEnv(), object );
    if( JDEBUG ) printf("%s(%d) jniBarJava::bar(%p):jni_objectBase(%p)\n", __FILE__, __LINE__, this, jni_objectBase);
    jweak jni_object = jni_objectBase->jni_getJava();
    if( JDEBUG ) printf("%s(%d) jniBarJava::bar(%p)\n", __FILE__, __LINE__, this);
    bool reply;
    if( JDEBUG ) printf("%s(%d) jniBarJava::bar(%p)\n", __FILE__, __LINE__, this);
    jobject jni_reply = jni_invoke_jobject( reply, jni_getEnv(), jni_getJava(), "bar", "(Ljava/lang/String;LFooBar/Bar;)LFooBar/Bar;", jni_message, jni_object );
    if( ! reply ) return Bar::bar( message, object );
    if( JDEBUG ) printf("%s(%d) jniBarJava::bar(%p)\n", __FILE__, __LINE__, this);
    return reinterpret_cast<Bar *>( jniRoot::jni_getNativeSelf( jni_getEnv(), jni_reply ) );
};
    
Bar * jniBarJava::jni_getNative()
{
    return this;
};

void jniBarJava::jni_destroy()
{
    delete this;
};


This is the Java wrapper class for native C++ class Bar.
  • The constructor passes the JNI environment and the handle to the Java part of the instance, the Java self, to functions inherited from the technical superclass jniFooBase (described below). With these values a link to the Java proxy can established via JNI during virtual function callbacks (see function bar()).
  • The destructor has no function yet.
  • The function bar() is the work horse: it emulates Java overrides of the original C++ function Bar::bar(). See below discussion.
  • The jni_getNative() function is a technical function which returns the so-called native self, a handle to the native C++ instance in question.
  • Same way, the jni_destroy() is a technical function which deletes the wrapper clas instance.
The mentioned technical functions provide a consistent interface to JNI++ technical bookkeeping in both kinds of wrapper classes for use in the JNI stubs.

Of course, the virtual function is the interesting point. Let's have a closer look at the implementation:

  • First, the passed parameters have to be converted from C++ to Java. There are several different cases (only two of them are shown in the code):
    simple types
    Conversion of simple types is straight forward as described in "Essential JNI" by Rob Gordon.
    strings
    Conversion of string is a little more complex, especially if UNICODE is involved. The above code shows a simplified snippet.
    array
    This is problematic. "Essential JNI" by Rob Gordon gives a starting point. Furthermore, there are ongoing discussions in the relevant forums.
    structs
    Rob Gordon's structConverter may be of help, but still not solved entirely...
    pointers and references to objects
    JNI++ tries to handle this by C++ wrappers below.
    Conversion between Java and C++ in both directions is a very sophisticated topic. It will (should) be discussed in a later paper.
  • Then, the Java self is inquired. This is a weak global reference to the object referenced by the constructor's self parameter. A weak global reference is used to pin down the Java self handle for the lifetime of the apdapter instance. The C++/Java proxy keeps such a weak global reference especially for the
  • Call of the Java proxy's member function with matching signature.
  • If no such member exists, the function is not overridden at all adn the native C++ function is in duty.
  • Finally, the reply of the Java call is converted from Java to C++ and returned to the caller.

2.2. C++ Wrapper Classes

Native C++ methods may return objects, either as return value or through parameters. In C++ there are three possiblilies to pass an object to the caller:
  1. as a reference to the object
  2. as a pointer to the object
  3. as a copy to the object
In case of references and pointers, the object may or may not be managed by JNI++ already. If a copy of an object is returned, JNI++ has not seen this object yet anyway. If such an object is to be called from Java, special measures must be taken. JNI++ takes the following approach: If an object is passed from C++ to Java which is not yet under JNI++ control, a C++ wrapper instance for that class is created. The methods of the C++ wrapper are used by the JNI stub code to call the native methods.

Note that there is no need to prepare the C++ wrappers for subclassing: the objects are created already as instances of a concrete C++ class.

In order to ease access to the two different kinds of wrapper classes, a common base class is introduced which defines a uniform interface for calling native C++ methods from the JNI stub. This class is described below.



class Bar : C++ wrapper definition

#ifndef jniBarNative_h
#define jniBarNative_h

#include "jniext.h"
#include "jniBarBase.h"
#include "Bar.h"

class Bar;

/*
{group:wrapper}
Summary: Native wrapper for C++ class Bar
Purpose:
  This is the JNI++ Native wrapper for the native C++ class Bar. 
Native wrappers are used whenever the native C++ library creates an instance which
must be passed to Java. Each native wrapper for a C++ instance holds an handle
to this C++ instance. It constitutes the C++ part of a Java instance of 
the Java subclass. It implements the interface functions from jniBarBase
an superclasses for easy access in the JNI++ stub code.

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 jniBarNative
: public jniBarBase
{
public:
/*
Summary: create a native wrapper for C++ instance
Parameters:
	env - the JNI environment for that call
	self - the native self
Purpose:
  This function creates a native wrapper for the given C++ instance.
Such a native wrapper is required whenever C++ objects are passed
to Java.
*/
    jniBarNative(JNIEnv * env, Bar * self );

    virtual ~jniBarNative();

/*
Summary: Destroy the C++ part of the instance
Purpose:
  This is the native 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();

/*
Summary: Get the native self
Returns: The native self, a handle to the C++ part of the controlled instance
Purpose:
  This is the native 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 controlled instance.
*/
    virtual Bar * jni_getNative();
private:
/* {secret} */
    Bar * thisNative;
};

#endif
The definition of the C++ wrapper class is fairly straight forward:
  1. It is a subclass of the respective common base class only. It IS NOT a subclass of the original native C++ class (and it can't be).
  2. However, it HAS A handle to the instance managed: the private variable this native serves for this.
  3. The jni_getNative() function is a technical function which returns the so-called native self, the handle to the native C++ instance in question.
  4. Same way, the jni_destroy() is a technical function which deletes the wrapper clas instance.
As already mentioned, the technical functions provide a consistent interface to JNI++ technical bookkeeping in both kinds of wrapper classes for use in the JNI stubs.


class Bar : C++ wrapper implementation

#include "jniBarNative.h"
#include "jniBarNative.h"
#include "Bar.h"

jniBarNative::jniBarNative(JNIEnv * env, Bar * self )
: thisNative( self )
{
    jni_setEnv( env );
    jni_setJava( ::jni_createInstance( env, "FooBar/Bar", "(I)V", (int) dynamic_cast<jniRoot *>( this ) ) );
};

jniBarNative::~jniBarNative()
{
    if( JDEBUG ) printf(" --- jniBarNative::~jniBarNative(%p)\n", jni_getJava());
};

Bar * jniBarNative::jni_getNative()
{
    if( JDEBUG ) printf(" --- jniBarNative::jni_getBar(%p)\n", thisNative);
    return thisNative;
}

void jniBarNative::jni_destroy()
{
    delete this;
};
The implementation of the C++ wrapper class isn't to complicated either:
  1. The constructor stores the native self of the managed object in the private variable thisNative. The it saves the passed JNI environment with the help of the inherited function jni_setEnv(): Then it creates a Java proxy (!) for the managed object and stores the resulting Java self with the help of the inherited function jni_setJava().
  2. The destructor is for test purposes only.
  3. The jni_getNative() function returns the native self, which is in case of C++ wrappers the handle stored in the private variable thisNativ. Have a look back to the Java wrapper implementation to realize the difference.
  4. The jni_destroy() function deletes the wrapper class instance.

2.3. Wrapper Base Classes

2.3.1 Common Wrapper Base for particular C++ Classes

As pointed out above, a common base class for both kinds of wrapper classes of a particular C++ class handled by JNI++ is generated by the JNI++ generator. In Java this would simply be an interface definition to be implemented by the repective wrapper class. Since C++ does not know about interfaces, a pure class is used instead:


class BarBase : C++ wrapper common base definition

#ifndef jniBarBase_h
#define jniBarBase_h

#include "jniRoot.h"
#include <list>
using namespace std;
class Bar;
class jniBarBase;
/*
Summary: list of all active  instances of class Bar
Purpose:
  This list is maintained in order to prevent duplicate instances
of native wrappers for the same native C++ instance
*/
typedef list<jniBarBase *> InstanceListType;
    
/*
{group:wrapper}
Summary: the base class for both types of wrappers for class Bar
Purpose:
  This class provides the interface for convenient access of both kinds of wrappers,
namely native and Java wrappers.  
*/
class jniBarBase
: virtual public jniRoot
{
public:
    jniBarBase();
    virtual ~jniBarBase();

/*
Summary: get the native self
Returns: the native self 
Purpose:
 This interface function returns the native self, a handle (pointer) to the
C++ instance in question.
*/
    virtual Bar * jni_getNative() = 0;

/*
Summary: find the JNI base of an C++ instance
Returns: the JNI base if found, NULL otherwise
Parameters:
	nativeSelf - the native self of the searched instance 
Purpose:
 This function searches the list of all C++ wrapper instances of class
 Bar for the JNI base belonging to nativeSelf.
*/
    static jniBarBase * FindJNIBase( Bar * nativeSelf );
private:
/* {secret} */
	static InstanceListType m_InstanceList;
};

#endif
  1. The common base class for the two wrapper classes of a particular C++ class is itself a subclass of the common superclass for all JNI++ wrapper classes (described below).
  2. The only thing more is the interface definition for the jni_getNative() functio to be implemented by the two wrapper classes.



class Bar : C++ wrapper common base implementation

#include "jniBarBase.h"
#include "Bar.h"

jniBarBase::jniBarBase()
{
	m_InstanceList.push_front( this );
};

jniBarBase::~jniBarBase()
{
	m_InstanceList.remove( this );
};


InstanceListType jniBarBase::m_InstanceList;

jniBarBase * jniBarBase::FindJNIBase( Bar * nativeSelf )
{
    jniBarBase * reply = dynamic_cast<jniBarBase *>( nativeSelf );
	if( reply )
		return reply;
	for( InstanceListType::iterator iX = m_InstanceList.begin(); iX != m_InstanceList.end(); iX++ )
	{
		reply = *iX;
		if( reply->jni_getNative() == nativeSelf )
			return reply;
	}
	return 0;
};

There is no real implementation, it is an interface, that's all! Constructor and destructor are for testing purposes only.

2.3.2. Common Base for all Wrappers

There is a handful of services useful for all JNI++ wrapper classes. These are packed into the common superclass jniBase:


class jniBase : common base definition for all wrapper classes

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class FooBar_Base */

#ifndef _Included_FooBar_Base
#define _Included_FooBar_Base
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     FooBar_Base
 * Method:    jni_destroy
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_FooBar_Base_jni_1destroy
  (JNIEnv *, jobject, jint);

#ifdef __cplusplus
}
#endif
#endif
The services the jniBase class provides are:
  • Access to the native self stored in a given Java proxy object (jni_getNativeSelf(), jni_setNativeSelf())
  • Interface definition for the jni_destroy() function to be implemented in the wrapper classes.
  • Access to the JNI environment handle of an wrapper (jni_getEnv(), jni_setEnv())
  • Access to the Java selft of an wrapper (jni_getJava(), jni_setJava())
  • Storeage of the JNI environment and the Java self of an wrapper (variables thisEnv and thisJavaSelf).

The implementation of jniBase is straight forward: into the common superclass jniBase:



class jniBase : common base implementation for all wrapper classes

#include "jniext.h"
#include "jniBase.h"
#include "jniRoot.h"

/*
{group:JNIstub}
Summary: destroy native C++ instance
Parameters:
	env - the JNI environment for that call
	self - the Java self
    nativeSelf - the native self
Purpose:
  This function destroys the controlled instance of nativeSelf. It is called during
finalize() of Java proxy class Base.
*/
JNIEXPORT void JNICALL Java_FooBar_0005cBase_jni_1destroy(JNIEnv * env, jobject self, jint nativeSelf )
{
    if( JDEBUG ) printf(" --- Java_FooBar_0005cBase_jni_1destroy()\n");
    ((jniRoot *)(nativeSelf))->jni_destroy();
};



The services the jniBase class are implemented as follows:
  • Access to the native self stored in a given Java proxy object (jni_getNativeSelf(), jni_setNativeSelf()) is simply access of the jni_Nativeself attribute of the Base Java proxy superclass.
  • Access to the JNI environment handle of an wrapper (jni_getEnv(), jni_setEnv()) and
  • Access to the Java self of an wrapper (jni_getJava(), jni_setJava()) are simple accessor functions.

previous  home  next 

Hosted by www.Geocities.ws

1