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:
- as a reference to the object
- as a pointer to the object
- 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:
- 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).
- However, it HAS A handle to the instance managed: the private variable this
native serves for this.
- The jni_getNative() function is a technical function which returns the so-called
native self, the handle to the native C++ instance in question.
- 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:
- 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().
- The destructor is for test purposes only.
- 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.
- 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
- 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).
- 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