Having explored the basics of the native method interface, let's shift to one of the most common uses of native methods-as an interface to existing C and C++ libraries. First, you learn two methods (static and object-oriented) of building interface classes to existing C libraries using a very simple signal processing library as an example.
Next, you investigate interfacing to C++ libraries, developing a wrapper system for overcoming Java's C-only native method interface by using a very simple 3D library as an example. As you go along, you examine all the problems in interfacing to this library.
Finally, in the third section of this chapter, you look at some common interfacing problems and tips on how to solve them. You first learn how to handle a missing native library when running an applet in a browser. Then you look at how to overcome some of Java's object-oriented shortcomings when interfacing to a legacy C++ library. You learn how to interface to C++ libraries using templates, multiple inheritance, and operator overloading.
Due mostly to C's popularity, a tremendous number of C libraries exist in the development world today. Because of either the development time necessary to develop these legacy libraries, the sheer performance required, or the difficultly in porting the library, it may be very difficult to port the library entirely to Java. Instead, it may be necessary to develop a Java interface to the library.
This interfacing of C with Java is made difficult largely for one reason: Java is entirely object-oriented. Unlike C++, Java does not enable you to use procedural functions within the context of an object-oriented program. Every construct must involve an object of some sort.
There are two general methods of working around this. First, you could sit down with the list of functions in the C library and carve it up into functionally related blocks. For each block of the library, you could then develop a class that contains each function as a static Java method within the class. The class is, of course, then titled with some moniker that indicates the relation of all the functions it contains.
Although this is probably the fastest way to convert your library over to Java, it may not be the smartest. Although your current C library users will have no difficulties getting used to the Java interface, your new Java users may because it isn't object-oriented. As a solution to this dilemma, you should consider the feasibility of developing an object-oriented interface to your library in addition to the static member class interface. This way, both sets of users are happy-those used to your C interface and those used to your C++ interface.
As a simple example of the first alternative, assume that you have the very simple C library represented by the prototype shown in Listing 32.1. This library implements three useful signal processing functions: a real numbered fast Fourier transform (realFFT), a sine Fourier transform (sinft), and a cosine Fourier transform (cosft). For each of these C functions, the variable samples contains the signal samples, and n is the number of samples. The result is returned in place through the ft parameter.
Listing 32.1. A simple C FFT library.
void realFFT(float samples[], long n, float ft[]);
void sinft(float samples[], long n, float ft[]);
void cosft(float samples[], long n, float ft[]);
The static Java implementation is shown in Listing 32.2.
Listing 32.2. The static Java implementation.
class FFTlibrary {
public native static void realFFT(float samples[], int n,
float FFTresult[]);
public native static void sinft(float samples[], int n,
float FFTresult[]);
public native static void costft(float samples[], int n,
float FFTresult[]);
}
This library would then have an interface C file that contains a map to the C library function, just like the native interface files you considered in the last chapter. This interface file is shown in Listing 32.3.
Listing 32.3. The interface file.
#include <native.h>
#include <FFT.h>
void FFTlibrary_realFFT(float samples[],
long n,
float FFTresult[]) {
realFFT(samples, n, FFTresult);
}
void FFTlibrary_cosft(float samples[],
long n,
float FFTresult[]) {
cosft(samples, n, FFTresult);
}
void FFTlibrary_sinft(float samples[],
long n,
float FFTresult[]) {
sinft(samples, n, FFTresult);
}
Instead of a static class interface, you could instead develop a set of Java classes that uses the underlying C library to perform the complex operations but gives the Java library an object-oriented feel. For example, shown in Listing 32.4 is the DataSample object, which implements storage for a set of signal samples and provides methods to calculate the real FFT, the cosine FT, and the sine FT.
Listing 32.4. The DataSample class.
package mySigProcLib;
class DataSample {
public void AddSample(float sample) {
// ...Some signal management logic here...
}
public void DeleteSample(int I) {
// ...Some signal management logic here...
}
public native void realFFT(float FFTresult[]);
public native void cosFFT(float FFTresult[]);
public native void sinFFT(float FFTresult[]);
int nSamples;
float fSamples[];
}
The implementation of the DataSample class is shown in Listing 32.5. There's nothing new from the last chapter in this implementation, but it does show how to structure your C implementation to achieve the feel of an object-oriented library in your Java interface object.
Listing 32.5. Implementation of the DataSample class.
#include <native.h>
#include <FFT.h>
void mySigProcLib_DataSample_realFFT(
struct mySigProcLib_DataSample* hthis,
struct HArrayOfFloat* FFTresult
) {
realFFT(unhand(unhand(hthis)->fSamples)->body,
unhand(hthis)->nSamples,
unhand(FFTresult)->body);
}
void mySigProcLib_DataSample_cosFFT(
struct mySigProcLib_DataSample* hthis,
struct HArrayOfFloat* FFTresult
) {
cosFFT(unhand(unhand(hthis)->fSamples)->body,
unhand(hthis)->nSamples,
unhand(FFTresult)->body);
}
void mySigProcLib_DataSample_sinFFT(
struct mySigProcLib_DataSample* hthis,
struct HArrayOfFloat* FFTresult
) {
realFFT(unhand(unhand(hthis)->fSamples)->body,
unhand(hthis)->nSamples,
unhand(FFTresult)->body);
}
With the explosion in interest in object-oriented design has come an explosion in the number of available C++ class libraries. This section extends the discussion of interfacing from the last section to developing an interface to existing C++ libraries. To make the description of this design process more concrete, you walk through an example of taking an existing C++ class library and developing a parallel set of Java classes that transparently provide the same look and feel as the C++ classes.
To demonstrate an interface between Java objects and C++ objects, let's use a few components of a very primitive 3D graphics class library, My3D.
Let's assume that the My3D C++ library is either too performance-sensitive to be converted to Java or redevelopment of the Java library would involve so much time that developing a Java interface to the C++ class is a better investment. Before diving in with the implementation of your interface class, you should read through the steps involved in the implementation of the My3D class to get an idea of how much development is involved. If it looks like developing the interface will take longer than porting your library from C++ to Java, port your library instead of developing an interface. This seems like an obvious point, but several developers have taken the hard road instead of the easy one.
Listing 32.6 contains the World object, the first C++ object you will consider in your simple 3D library. The World object is responsible for handling the details of attaching and detaching objects from a 3D scene. The method AttachNode, as you may have guessed, is responsible for taking a pointer to a Node object and attaching this Node to the scene graph. Likewise, DetachNode is responsible for detaching Nodes from the scene graph, either from the end of the graph or the Node specified by RemoveNode.
The World class also contains a list of pointers to Node objects in the private section of the definition, which is used to store the scene graph. However, as you'll see when you implement the World class, this information isn't necessary to interface the class with Java-you really have to know only the publicly available methods and variables for the class.
Listing 32.6. World C++ class definition.
class World {
public:
void AttachNode (Node* theNode);
Node* DetachNode (Node* RemoveNode);
private:
Node** NodeList;
};
The next class in the My3D C++ class library is the Node class, shown in Listing 32.7. This class contains the mechanisms necessary to give an object in your World a location. This superclass is the base class for all objects that appear in the rendered 3D scenes. The class contains only two accessor methods, SetLocation and GetLocation, which are used to set and retrieve the position of the node in space. The data structure PointFW_t is used to encapsulate these points (see List-ing 32.8).
Listing 32.7. Node C++ class definition.
class Node {
public:
void SetLocation (PointFW_t& loc);
PointFW_t GetLocation();
private:
PointFW_t theLocation;
}
Listing 32.8. PointFW_t C++ class definition.
typedef struct PointFW_t {
float x;
float y;
float z;
float w;
} PointFW_t;
The Light class is a subclass
of Node, which makes it attachable
within your World scenes
(see Listing 32.9). Because it is a subclass of Node,
it has position. The Light
object models a light in the scene by adding a direction and color
to the subclass with the associated accessors,
Set/GetDirection() and Set/GetColor().
Listing 32.9. Light C++ class definition.
class Light : public Node {
public:
void SetDirection (PointF_t& dir);
PointF_t& GetDirection();
void SetColor (ColorF_t& theColor);
ColorF_t& GetColor();
private:
PointF_t TheDirection;
ColorF_t TheColor;
};
This class also introduces two new data structures, PointF_t and ColorF_t, as shown in Listings 32.10 and 32.11. The PointF_t data structure encapsulates a vector that points in the direction <x,y,z>. The ColorF_t type represents a color with the red, green, and blue components <r,g,b>.
Listing 32.10. PointF_t C++ struct definition.
typedef struct PointF_t {
float x;
float y;
float z;
} PointF_t;
Listing 32.11. ColorF_t C++ struct definition.
typedef struct ColorF_t {
float r;
float g;
float b;
} ColorF_t;
The final class in your My3D graphics library (if only real 3D graphics class libraries could be so simple!) is the Geometry class. This class is also a subclass of Node, which enables the class library user to specify a geometric object in the graphics scene. The constructor for the Geometry class takes all the information necessary to specify the object in space: the number of polygons and vertices, the points in space for all the vertices and the vertex normals, and the ordering of which points go with which polygon.
There are also two methods to control rotations and scaling around its central location specified with SetLocation() from the Node class: RotateObject() and ScaleObject(). The Geometry class is shown in Listing 32.12.
Listing 32.12. Geometry C++ class definition.
class Geometry : public Node {
public:
Geometry(long INnPolygons, long INnVertices,
PointF_t* INpVertices, PointF_t* INpNormals,
long* INVerOrder);
void RotateObject(double theta, PointF_t* RotationAxis);
void ScaleObject(PointF_t* ScaleFactors);
private:
long nPolygons;
long nVertices;
PointF_t* pVertices;
PointF_t* pNormals;
PointF_t* pFacets;
PointF_t* pTexture;
ColorFA_t* pColors;
long* VerOrder;
}
With that definition finished, you can move on to the real task of building Java objects that interface with these C++ classes. Before you can start developing interface classes that correspond one-to-one with C++ classes, however, you first need a class that encapsulates all the interface information that you need about your C++ class, the InterfaceObject class (see Listing 32.13).
Listing 32.13. Java InterfaceObject definition.
package My3D;
public class InterfaceObject {
// Returns an ordinal number that uniquely identifies the object.
public int KindOf() {
return (ObjKindOf);
}
// Returns True if the object type passed to IsOf matches this
// object's type.
public boolean IsOf(int k) {
if (k == ObjKindOf) {
return(true);
} else {
return(false);
}
}
public int hCPPObj;
public int ObjKindOf;
static {
System.load("my3d.dll");
}
}
The InterfaceObject class will be the base for all your object classes. It contains two methods to help with object identification (you'll see where this is useful in a few sections): KindOf() and IsOf(). KindOf() is used to access the object type. This object type is a constant that uniquely identifies the object's type. The IsOf() method tests the object type passed to it, to see whether it is the same as this object's type, by using the internal constant as a check.
The InterfaceObject class also encapsulates two variables: hCPPObj and ObjKindOf. ObjKindOf contains the ordinal type of the object. hCPPObj contains a handle to the parallel C++ instantiation of this object. You'll see how both these variables are set in the next section.
Let's start interfacing your library to Java with the World class; the implementation of the Java class is shown in Listing 32.14.
Listing 32.14. World Java class definition.
package My3D;
public class World extends InterfaceObject {
public native void constructor();
public World() {
constructor();
}
public native void finalize();
public native void AttachNode (Node theNode);
public native Node DetachNode (Node afterNode);
}
Your first impression of this class should be that it looks very similar to the C++ one. This is good! However, you have to learn a few more things about native implementation before this illusion can become a reality for your Java class library users.
Let's start at the beginning, with the Java World class constructor. As you saw in Listing 32.14, the World Java implementation class calls a native constructor in its constructor. You want your native constructor to accomplish the following tasks:
The native constructor implementation for the World class is shown in Listing 32.15.
Listing 32.15. World interface constructor.
long My3D_World_AllocLength = 0;
#define INITIAL_My3D_World_ALLOC 2
World** My3D_World_ObjPtr = NULL;
long* My3D_World_ObjRef = NULL;
/*
** Title: constructor()
** Function: Instantiates the C++ World class and sets the handle
** in the Java object.
*/
void My3D_World_constructor(struct HMy3D_World *jthis) {
// Check to see if the initial allocation for the
// World class has been done yet. If not, allocate
// the necessary data structures.
if (My3D_World_AllocLength == 0) {
My3D_World_AllocLength =
My3D_World_Resize(INITIAL_My3D_World_ALLOC);
}
// Search for an empty position (empty position == NULL).
long pos;
for ( pos=0;
(pos != My3D_World_AllocLength) &&
(My3D_World_ObjPtr[pos] != NULL);
pos++ )
;
if (pos == My3D_World_AllocLength) {
// All allocated positions are full.
// So use exponential allocation to create some more.
My3D_World_AllocLength =
My3D_World_Resize(My3D_World_AllocLength*2);
}
My3D_World_ObjPtr[pos] = new World();
// Stub for handling out of memory condition.
// Handle as desired in your implementation.
assert (My3D_World_ObjPtr[pos] != NULL);
// Increment Reference counter.
My3D_World_IncRefCntr(pos);
// Store handle (== position in array) for this
// object.
unhand(jthis)->hCPPObj = pos;
}
In every instantiable class that you consider in this chapter, your interface functions maintain a list of pointers to all the C++ objects you have instantiated indirectly by instantiating their Java interface class. In the previous constructor, the first statement checks to see whether this list needs to be initialized:
if (My3D_World_AllocLength == 0) {
My3D_World_AllocLength =
My3D_World_Resize(INITIAL_My3D_World_ALLOC);
}
My3D_World_Resize is a helper function that is responsible for increasing and decreasing the amount of allocation for the pointer list to World objects. There is a similar function for each of the interface files you consider in this chapter. The code for the My3D_World_Resize function is shown in Listing 32.16.
Listing 32.16. My3D_World_Resize function definition.
/*
** Title: Resize()
** Function: Resizes the array of World pointers.
** Frequent callers should use a
** exponential size increase (ie. 2x or 4x) to
** reduce memory thrashing.
*/
long My3D_World_Resize(long newsize) {
World** NewObjPtr = new World* [newsize];
long* NewRefPtr = new long[newsize];
long i;
for (i=0; i != My3D_World_AllocLength; i++) {
NewObjPtr[i] = My3D_World_ObjPtr[i];
NewRefPtr[i] = My3D_World_ObjRef[i];
}
for (; i != newsize; i++) {
NewObjPtr[i] = NULL;
NewRefPtr[i] = 0;
}
delete My3D_World_ObjPtr;
delete My3D_World_ObjRef;
My3D_World_ObjPtr = NewObjPtr;
My3D_World_ObjRef = NewRefPtr;
return (newsize);
}
Unused pointers in the C++ object array are set to NULL in the My3D_World_Resize function. After checking in the interface constructor code to see whether the pointer list has been initialized, the constructor next tries to find an empty slot in the pointer list:
long pos;
for ( pos=0;
(pos != My3D_World_AllocLength) &&
(My3D_World_ObjPtr[pos] != NULL);
pos++ )
;
The loop quits before it reaches My3D_World_AllocLength if it does find an empty slot. If it doesn't, the constructor allocates new pointer space:
if (pos == My3D_World_AllocLength) {
// All allocated positions are full.
// So use exponential allocation to create some more.
My3D_World_AllocLength =
My3D_World_Resize(My3D_World_AllocLength*2);
}
Notice that it allocates pointer space exponentially-every time the constructor is forced to resize the pointer list, it does so to twice the size of the last pointer list size. This helps reduce the number of times that memory allocation is needed for large object databases, without wasting memory for small object databases. Because memory allocation is such an expensive event, you should implement this or an equivalent scheme in your interface code, unless the size of the pointer array is known beforehand or the number of objects typically needed is very small (less than 8).
With an empty spot on the list found for the instantiated pointer, the constructor allocates a World object and stores it in the list:
My3D_World_ObjPtr[pos] = new World();
The World object's constructor doesn't take any arguments, and for that reason, none are passed into the World constructor. If your constructor does take arguments, this is where you should change your routines to pass them in. If your class contains multiple constructors, you have to create multiple constructor interfaces in your implementation file to handle each variant-ugly, but because C does not have any functionality such as overloading in C++, it is the only way to accomplish this.
Directly following this allocation is an assert function call that checks to make sure that the memory allocation occurred safely. If it didn't, the library will fail with the assertion. For your library, you should replace this assertion statement so that your program can handle this problem in a graceful manner.
As you'll see in later sections, the underlying C++ library can sometimes return pointers to objects during calls to C++ methods. To mimic this operation in your Java library, you will want to add functionality to translate this pointer to one of our Java objects. Because of this, multiple Java interface objects can refer to a single C++ pointer, so you need to maintain a reference counter to keep track of how many objects have a copy:
My3D_World_IncRefCntr(pos);
This reference counter is implemented as an array of integers called My3D_World_ObjRef. The reference count for an object is stored in the same position in the array as its pointer is in My3D_World_ObjPtr, or equivalently, at the hCPPObj position stored in the Java interface object. The implementation of the IncRefCntr function is shown in Listing 32.17.
Listing 32.17. IncRefCntr function definition.
/*
** Title: IncRefCntr()
** Function: Performs automatic memory management.
** Increments the reference counter
** for the World object.
*/
void My3D_World_IncRefCntr(long ThehCPPObj) {
My3D_World_ObjRef[ThehCPPObj]++;
}
The DecRefCntr function, used when a Java interface object using a pointer moves out of scope, decrements the reference counter. It then checks to see whether any other Java objects are still using this pointer, indicated by the reference count being greater than 0. If not, DecRefCntr deletes the underlying object. The implementation of DecRefCntr is shown in Listing 32.18.
Listing 32.18. DecRefCntr function definition.
/*
** Title: DecRefCntr()
** Function: Performs automatic memory management.
** When the number of objects referencing
** the object equals zero, the object is
** deallocated.
*/
void My3D_World_DecRefCntr(long ThehCPPObj) {
My3D_World_ObjRef[ThehCPPObj]--;
if (My3D_World_ObjRef[ThehCPPObj] == 0)
My3D_World_ObjPtr[ThehCPPObj]->Destroy();
}
}
The final statement in the World constructor assigns the position in the pointer list to the Java interface object's hCPPObj variable so that when a C++ object pointer is needed, it is available by lookup in the pointer table. This completes all the tasks that you set out to do in your World constructor.
Next, let's focus on how you implement the methods within the Java World Interface. This task really boils down to translating a call to one of these Java methods to an equivalent call with the same data (but possibly in a differing C++ format) to the C++ version of the class.
Shown in Listing 32.19 is the implementation for the AttachNode method.
Listing 32.19. AttachNode implementation.
/*
** Title: My3D_World_AttachNode()
** Function: Routes the Java call from the Java class method
** AttachNode to the C++ class method AttachNode for the
** instantiation handle passed in hthis.
*/
void My3D_World_AttachNode (
struct HMy3D_World *hthis,
struct HMy3D_Node *hNode) {
My3D_World_ResolvePtr(hthis)->AttachNode(
My3D_Node_ResolvePtr(hNode)
&nbs p; );
}
The AttachNode function prototype should look very familiar to you from the last chapter. This function is passed the Java World object handle as the first parameter and a handle to the Node to be attached as the second. You haven't explored the interface constructor of the Node object yet, but you really don't need to-it is completely analogous to the one you saw for the World object. Literally, only the names have been changed.
The body of the AttachNode native method contains only one statement. This call to My3D_World_ResolvePtr looks up the World C++ pointer referenced by statement and calls this World object's AttachNode method with the Node object referenced by the hNode and resolved by using My3D_Node_ResolvePtr.
The functions My3D_World_ResolvePtr and My3D_Node_ResolvePtr referenced earlier are used to find the C++ pointers to the Node and World objects. These functions take a handle to an InterfaceObject and return the C++ pointer to the Java interface object. They do this translation using the hCPPObj handle for the World class. The implementation looks as shown in Listing 32.20.
Listing 32.20. ResolvePtr function definition.
/*
** Title: ResolvePtr()
** Function: Takes a handle to the Java World class
** and resolves it to the associated 3DW
** C++ pointer.
*/
World* My3D_World_ResolvePtr(struct HMy3D_World *jthis) {
return( My3D_World_ObjPtr[unhand(jthis)->hCPPObj] );
}
This is a very simple function. It retrieves the object handle stored in hCPPObj and returns the World object pointer referenced by it. You'll see a more complicated example of this function when you consider the implementation of this function for an abstract class such as the Node class.
The DetachNode method object is the final explicit method needed for the World object. Its implementation is shown in Listing 32.21.
Listing 32.21. DetachNode function definition.
/*
** Title: My3D_World_DetachNode()
** Function: Routes the Java call from the Java class method
** AttachNode to the C++ class method DetachNode for the
** instantiation handle passed in hthis. It takes the
** returned CPPNode and translates it back into a Java
** handle.
*/
struct HMy3D_Node* My3D_World_DetachNode(
struct HMy3D_World *hthis,
struct HMy3D_Node *hNode) {
Node* CPPNode;
CPPNode = My3D_World_ResolvePtr(hthis)->DetachNode(
My3D_Node_ResolvePtr(hNode)
&nbs p; );
struct HMy3D_Node *hNode;
ClassClass *ccNode = NULL;
ccNode = FindClass(NULL, "My3D/Node\0", TRUE);
assert(ccNode != NULL);
hNode = (struct HMy3D_Node*)
execute_java_constructor(NULL,"My3D/Node\0",
ccNode,"()");
assert(hNode != NULL);
My3D_Node_PtrEmul(CPPNode, hNode);
return(hNode);
}
There are a lot of new things in the implementation of the My3D_World_DetachNode function. First, it returns a class that isn't one of the standard set of objects available in Java. This is the main reason that the function is so complicated. In the last chapter, you learned how to return a java.lang.String class. However, the String class has a Java interpreter call, makeJavaString, that enables you to instantiate a String for the return. Because Node is a developer-defined class and is not from the Java class library, there is no equivalent call for Node in the Java interpreter API. Instead, you need to learn how to use the interpreter calls to instantiate and return arbitrary Java objects.
The first two lines of the function are pretty straightforward. The function resolves the pointer for the World object and the passed Node object and calls the DetachNode method. The DetachNode method returns a pointer to the Node detached, which it stores in CPPNode.
Now, you want to find the handle of the Java object that corresponds to the returned C++ pointer of the detached Node and create a Node handle that incapsulates this handle to return.
Before you instantiate a Java Node class, you need to fill out a ClassClass structure (discussed in Chapter 31, "The Native Method Interface"). You did this in My3D_World_DetachNode using the following statement:
ccNode = FindClass(NULL, "My3D/Node\0", TRUE);
This statement tells the Java interpreter to look up the class information for the My3D.Node Java class. The first parameter, for which you passed NULL, enables you to specify an interpreter to service this request through an ExecEnv* (the type ExecEnv is defined in interpreter.h, but an understanding of its contents is not necessary for this discussion). Passing NULL indicates that you want to use the default interpreter to look up this information. Unless you are writing native programs that use multiple interpreters (you know if you are-it's a painful experience), you can ignore this parameter and pass NULL.
The third parameter tells the interpreter whether to resolve the name passed into FindClass. You should always pass TRUE for this parameter. Sun hasn't documented exactly what this function does if you don't, so as of this writing, there is no definitive reason.
Next, you want to instantiate a Java Node
that you can use to return the C++ Node
returned
from the DetachNode method.
To do this, you use the Java interpreter function execute_java_constructor:
hNode = (struct HMy3D_Node*)
execute_java_constructor(NULL,"My3D/Node\0",
ccNode, "()");
The execute_java_constructor function takes an ExecEnv* as the first parameter and the name of the class as the second, just as the FindClass function call did. For the third parameter, you should pass the ClassClass structure returned by FindClass in the last step.
The final parameter specifies the signature of the Node constructor you wish to call. This signature indicates the types that you wish to pass and receive from the invoked Java method. The previous passed signature ("()") is the simplest-a constructor that accepts no parameters and returns nothing. All constructors do not have a return type, but if you had a constructor in the Node class that accepted an int as a parameter for the constructor, its constructor would have been "(I)". The return type is specified after the closing parenthesis for methods that return values. For this hypothetical constructor that accepts an integer, you pass the integer parameter for the constructor as the fifth parameter. This parameter list is unbounded, like the parameter list for printf in the C language, so to pass in additional parameters, you merely keep adding them to the list. For example, the following passes a 4 to a hypothetical Node constructor that accepts an integer.
hNode = (struct HSolidCoffee_PointFW_t*)
execute_java_constructor(NULL,"My3D/Node\0",
ccNode,"(I)", 4);
Now that you have a Java Node instantiated, you want to do the following:
All of this is done by the function PtrEmul, as used in the My3D_World_DetachNode function:
My3D_Node_PtrEmul(CPPNode, hNode);
The My3D_Node_PtrEmul implementation is shown in Listing 32.22. It emulates a pointer reference system for your interface code.
Listing 32.22. Node PtrEmul function definition.
/*
** Title: My3D_Node_PtrEmul()
** Function: Takes the passed Node*, uses My3D_Node_FindHandle
** to find its handle and assigns this to the passed
** hWorld handle. In doing so, it adjusts the reference
** counters so that the system can keep track of the
** number of outstanding pointers.
*/
void My3D_Node_PtrEmul(Node* pNode,
struct HMy3D_Node* hNode) {
long hRetNode;
hRetNode = My3D_Node_FindHandle(pNode);
My3D_Node_IncRefCntr(hRetNode);
My3D_Node_DecRefCntr(unhand(hNode)->hCPPObj);
unhand(hNode)->hCPPObj = hRetNode;
}
The My3D_Node_PtrEmul function is relatively straightforward (you learn the internals later in this chapter). For now, all you need to know is that My3D_Node_FindHandle returns the handle to the Node object (or any of its subclasses) to which the C++ pointer refers.
After finding the corresponding handle for the Node C++ object, the function increments its reference count, because another Java Node object will now be using this pointer. Then, the function decrements the reference count for the target's current Node C++ object, because it will no longer be referenced by this Java Node. Finally, the function copies the handle into the target, completing the copy.
With that done, you now have a handle to a Java Node object that refers to the Node returned by DetachNode. To complete the interface function, you merely return this handle to the caller, which is done with the last statement of the function.
To complete the discussion of the Java World interface object, let's look at its My3d_World_FindHandle implementation. The My3D_World_FindHandle function is responsible for taking a pointer to a World object and translating it back into a handle that can be used to reference the World's My3D_World_ObjPtr array. The implementation for this is shown in Listing 32.23.
Listing 32.23. World FindHandle implementation.
/*
** Title: FindHandle()
** Function: Takes a pointer to a World class instantiation and
** looks up the associated Java handle to the object.
**
** IMPORTANT NOTE: To keep this code concise, a linear search
** algorithm was used. For class libraries that
** have methods that return many pointers per unit
** of execution time, this routine should be
** updated with a searching algorithm that resolves
** in much less time than O(n). We would suggest a
** hashing algorithm.
*/
long My3D_World_FindHandle(World *FindWorld) {
long pos;
for ( pos=0;
(pos != My3D_World_AllocLength) &&
(My3D_World_ObjPtr[pos] != FindWorld); pos++)
;
// Stub for appropriate handling of pointer not
// found errors.
assert (pos != My3D_World_AllocLength);
return (pos);
}
Because in the 3D graphics library you expect there will be only a small number of pointer lookups using the FindHandle function, this lookup function is written using a simple linear search function. If a large number of lookups are anticipated, this function should be rewritten with a hashing search method to reduce its performance impact.
The final interface function for the World class is the interface for the My3d_World_finalize method. If you are not familiar with this method, it is called when the Java garbage collector needs to dispose of an object because it has moved out of scope. This method enables the object to clean up after itself and perform any last minute maintenance tasks before the allocation for its data is freed. For your Java interface objects, you use this function to decrement the reference counter for the indicated object to keep your reference count coherent. The implementation of My3D_World_finalize is shown in Listing 32.24.
Listing 32.24. World finalize implementation definition.
/*
** Title: finalize()
** Function: Performs automatic memory management.
** When the number of objects referencing
** the object equals zero, the object is
** deallocated.
*/
void My3D_World_finalize(struct HMy3D_World *jthis) {
My3D_World_DecRefCntr(unhand(jthis)->hCPPObj);
}
The next interface implementation is the interface to the Node object. Listing 32.25 shows the Java object definition. It doesn't look much different from its C++ counterpart, which is helpful for any converts you might have from your C++ product.
Listing 32.25. Node Java class definition.
public class Node extends InterfaceObject{
public native void SetLocation (PointFW_t loc);
public native PointFW_t GetLocation();
public native finalize();
}
You need to define the Java version of the C++ PointFW_t structure so that you can present the implementation of the SetLocation and GetLocation methods. The Java version is very similiar to the C++ version, and its implementation is shown in Listing 32.26.
Listing 32.26. PointFW_t Java class definition.
class PointFW_t {
float x;
float y;
float z;
float w;
};
The native implementation of the SetLocation method is shown in Listing 32.27.
Listing 32.27. SetLocation implementation.
/*
** Title: My3D_Node_SetLocation()
** Function: Routes the Java call from the Java class method
** SetLocation to the C++ class method SetLocation for
** the instantiation handle passed in hthis.
*/
void My3D_Node_SetLocation (struct HMy3D_Node *hthis,
struct HMy3D_PointFW_t *hLocation) {
PointFW_t CPPLocation;
CPPLocation.x = unhand(hLocation)->x;
CPPLocation.y = unhand(hLocation)->y;
CPPLocation.z = unhand(hLocation)->z;
CPPLocation.w = unhand(hLocation)->w;
My3D_Node_ResolvePtr(hthis)->SetLocation(CPPLocation);
}
This implementation is very similiar to the AttachNode
interface function of the last chapter, but because the PointFW_t
class is a simple C++ object, you create a C++ PointFW_t
structure with each invocation of SetLocation
to pass the location into the C++ method instead of maintaining
a list of PointFW_t pointers.
Note |
You explicitly copy the members of the structure instead of dereferencing the pointer because there exists the chance of a mismatch between Java's representation of the PointFW_t class and C++'s representation. Specifically, the order of the elements of C++'s representation may be the reverse of the Java implementation, yielding SetLocation (<w,z,y,x>) when C++'s implementation may be SetLocation (<x,y,z,w>). Although the correlation is one-to-one with Visual C++ 4.0 and JDK 1.0, this isn't set in stone, and the safe programming practice is to assume no correlation and copy them one by one. In any case, compared to the overhead of calling the native method, the performance impact of these four copies is neglible. |
The native implementation of the GetLocation method is shown in Listing 32.28.
Listing 32.28. GetLocation implementation.
/*
** Title: My3D_Node_GetLocation()
** Function: Returns the current location of this node.
*/
struct HMy3D_PointFW_t* My3D_Node_GetLocation() {
PointFW_t CPPLocation;
CPPLocation = My3D_Node_ResolvePtr(hthis)->GetLocation();
struct HMy3D_PointFW_t *hLocation;
ClassClass *ccNode = NULL;
ccNode = FindClass(NULL, "My3D/PointFW_t\0", TRUE);
assert(ccNode != NULL);
hLocation = (struct HSolidCoffee_PointFW_t*)
execute_java_constructor(NULL, "My3D/PointFW_t\0",
ccNode,"()");
assert(hLocation != NULL);
unhand(hLocation)->x = CPPLocation.x;
unhand(hLocation)->y = CPPLocation.y;
unhand(hLocation)->z = CPPLocation.z;
unhand(hLocation)->w = CPPLocation.w;
return(hLocation);
}
The implementation of GetLocation is very similiar to the DetachNode implementation. The only difference is the deletion of the PtrEmul call and its replacement with a straight element-by-element copy of the returned PointFW_t location.
Before you leave the Node object, notice that the Node Java object doesn't have a constructor. This is important, because it means that no C++ Node objects can be instantiated. This was intended in the C++ design, because only the Light and Geometry objects were designed to be instantiated. You'll see how this works into the design when you consider the implementation of the My3D_World_PtrEmul function later in this chapter. Before you do that, let's lay the groundwork by discussing the two subclasses of Node: Light and Geometry.
Shown in Listing 32.29 is the Java implementation for the interface class to the Light object. Because Light is a subclass of Node in the C++ definition, it is also a subclass in the Java version. Whenever possible, you should always strive to keep your object hierarchies the same between your Java and C++ versions to prevent difficulties in interfacing the two libraries and preserve the same hierarchy with which your C++ users are familiar.
Because it can be instantiated, Light does have a constructor. The implementation of this constructor follows nearly exactly the same format as the World constructor.
The SetDirection and GetDirection methods also introduce a new class, PointF_t. The definition of this class is very similar to the PointFW_t class and is shown in Listing 32.30.
Listing 32.29. Light Java class definition.
public class Light extends Node {
private native void constructor();
public Light() {
constructor();
}
public native void finalize();
public void SetDirection(PointF_t dir);
public void PointF_t GetDirection();
}
Listing 32.30. PointF_t Java class definition.
class PointF_t {
float x;
float y;
float z;
};
The native implementation of the SetDirection and GetDirection interfaces for the Light class are shown in Listing 32.31.
Listing 32.31. SetDirection and GetDirection implementation.
/*
** Title: My3D_Light_SetDirection()
** Function: Routes the Java call from the Java class method
** SetDirection to the C++ class method SetDirection for
** the instantiation handle passed in hthis.
*/
void My3D_Light_SetDirection (struct HMy3D_Light *hthis,
struct HMy3D_PointF_t *hDirection) {
PointF_t CPPDirection;
CPPDirection.x = unhand(hDirection)->x;
CPPDirection.y = unhand(hDirection)->y;
CPPDirection.z = unhand(hDirection)->z;
CPPDirection.w = unhand(hDirection)->w;
My3D_Light_ResolvePtr(hthis)->SetDirection(CPPDirection);
}
/*
** Title: My3D_Light_GetDirection()
** Function: Returns the current Direction of this Light.
*/
struct HMy3D_PointF_t* My3D_Light_GetDirection() {
PointF_t CPPDirection;
CPPDirection = My3D_Light_ResolvePtr(hthis)->GetDirection();
struct HMy3D_PointF_t *hDirection;
ClassClass *ccLight = NULL;
ccLight = FindClass(NULL, "My3D/PointF_t\0", TRUE);
assert(ccLight != NULL);
hDirection = (struct HSolidCoffee_PointF_t*)
execute_java_constructor(NULL, "My3D/PointF_t\0",
ccLight,"()");
assert(hDirection != NULL);
unhand(hDirection)->x = CPPDirection.x;
unhand(hDirection)->y = CPPDirection.y;
unhand(hDirection)->z = CPPDirection.z;
unhand(hDirection)->w = CPPDirection.w;
return(hDirection);
}
As you can see, the implementation of the SetDirection and GetDirection classes is very similiar to that of the SetLocation and GetLocation methods in the Node class.
The final interface class that you need to know is the Geometry class. The Java interface class for the Geometry object is shown in Listing 32.32 and consists of the constructor and finalize methods for the Geometry class. The rest of the functionality for the Geometry class is provided by the Node class that it extends. Don't worry if you don't understand how the Geometry object specifies a polygon-it really isn't important to the discussion that follows. Instead, focus on how the interface between the Java and the C++ class works.
Listing 32.32. Geometry Java class definition.
public class Geometry extends Node {
private native void constructor(int nPolygons, int nVertices,
PointF_t pVertices[], PointF_t pNormals[],
PointF_t pFacets[], PointF_t pTexture[],
ColorFA_t pColors[], int VerOrder[]);
public Geometry(int nPolygons, int nVertices, PointF_t pVertices[],
PointF_t pNormals[], PointF_t pFacets[],
PointF_t pTexture[], ColorFA_t pColors[],
int VerOrder[]) {
constructor();
}
public native void finalize();
}
The Geometry class introduces one final class to your expanding library, the ColorFA_t class, which is responsible for specifying the colors of all the vertices in the object. Listing 32.33 shows the implementation of the ColorFA_t class.
Listing 32.33. ColorFA_t Java struct definition.
class ColorFA_t {
float r;
float g;
float b;
float a;
};
The implementation for the Geometry class constructor is shown in Listing 32.34. This constructor demonstrates how to handle arrays of passed objects in your interface code, which is a common source of confusion for native method users.
Listing 32.34. Geometry constructor implemention.
long My3D_Geometry_AllocLength = 0;
#define INITIAL_My3D_Geometry_ALLOC 2
Geometry** My3D_Geometry_ObjPtr = NULL;
long* My3D_Geometry_ObjRef = NULL;
/*
** Title: constructor()
** Function: Instantiates the C++ Geometry class and sets the
** handle in the Java object.
*/
void My3D_Geometry_constructorb(
struct HMy3D_Geometry *jthis,
short cNP, short cNV, HArrayOfObject* pAP,
HArrayOfObject* pAN, HArrayOfObject* pFN,
HArrayOfObject* pAT, HArrayOfObject* pAC,
// Check to see if the initial allocation for the
// Geometry class has been done yet. If not, allocate
// the necessary data structures.
if (My3D_Geometry_AllocLength == 0) {
My3D_Geometry_AllocLength =
My3D_Geometry_Resize(INITIAL_My3D_Geometry_ALLOC);
}
// Search for an empty position (empty position == NULL).
long pos;
for ( pos=0;
(pos != My3D_Geometry_AllocLength) &&
(My3D_Geometry_ObjPtr[pos] != NULL);
pos++ )
;
if (pos == My3D_Geometry_AllocLength) {
// All allocated positions are full.
// So use exponential allocation to create some more.
My3D_Geometry_AllocLength =
My3D_Geometry_Resize(My3D_Geometry_AllocLength*2);
}
Fixed16_t PassNP = cNP;
Fixed16_t PassNV = cNV;
// Copy the Vertex points from the Java Array into a C++ array
int len = obj_length(pAP);
PointF_t* PassAP = new PointF_t[len];
struct HMy3D_PointF_t* hAP;
for (i=0; i != len; i++) {
hAP = (struct HMy3D_PointF_t *) (unhand(pAP)->body)[i];
PointF_t* PtrAP = (PointF_t *) unhand(hAP);
PassAP[i] = *PtrAP;
}
// Copy the Normals from the Java Array into a C++ array
len = obj_length(pAN);
PointF_t* PassAN = new PointF_t[len];
struct HMy3D_PointF_t* hAN;
for (i=0; i != len; i++) {
hAN = (struct HMy3D_PointF_t *) (unhand(pAN)->body)[i];
PointF_t* PtrAN = (PointF_t *) unhand(hAN);
PassAN[i] = *PtrAN;
}
// Copy the Facet Normals from the Java Array into a C++ array.
len = obj_length(pFN);
PointF_t* PassFN = new PointF_t[len];
struct HMy3D_PointF_t* hFN;
for (i=0; i != len; i++) {
hFN = (struct HMy3D_PointF_t *) (unhand(pFN)->body)[i];
PointF_t* PtrFN = (PointF_t *) unhand(hFN);
PassFN[i] = *PtrFN;
}
// Copy the Texture points from the Java Array into a C++ array.
len = obj_length(pAT);
PointF_t* PassAT = new PointF_t[len];
struct HMy3D_PointF_t* hAT;
for (i=0; i != len; i++) {
hAT = (struct HMy3D_PointF_t *) (unhand(pAT)->body)[i];
PointF_t* PtrAT = (PointF_t *) unhand(hAT);
PassAT[i] = *PtrAT;
}
// Copy the Color points from the Java Array into a C++ array.
len = obj_length(pAC);
ColorFA_t* PassAC = new ColorFA_t[len];
struct HMy3D_ColorFA_t* hAC;
for (i=0; i != len; i++) {
hAC = (struct HMy3D_ColorFA_t *) (unhand(pAC)->body)[i];
ColorFA_t* PtrAC = (ColorFA_t *) unhand(hAC);
PassAC[i] = *PtrAC;
}
// Make a pointer to the vertex orders.
len = obj_length(pAV);
Fixed16_t* PassAV = (Fixed16_t *)(unhand(pAV)->body);
My3D_Geometry_ObjPtr[pos] = new Geometry(PassNP, PassNV,
PassAP, PassAN, PassFN, PassAT, PassAC, PassAV);
// Stub for handling out of memory condition.
// Handle as desired in your implementation.
assert (My3D_Geometry_ObjPtr[pos] != NULL);
// Increment Reference counter.
My3D_Geometry_IncRefCntr(pos);
// Store handle (== position in array) for this
// object.
unhand(jthis)->hCPPObj = pos;
// Delete all the temporary variables.
delete PassAP;
delete PassAN;
delete PassFN;
delete PassAT;
delete PassAC;
}
Take, for example, the pAP array, which contains a handle HArrayOfObject. When unhand is applied to it, it contains an array of handles to PointF_t objects for each of the points in its body element. By iterating down this array of handles, you can translate these Java PointF_t objects into C++ PointF_t objects.
Listing 32.35 shows the implementation of the My3D_Node_ResolvePtr function.
Listing 32.35. Node C++ ResolvePtr implementation.
Node* My3D_Node_ResolvePtr(struct HMy3D_Node *hNode) {
switch (unhand(jthis)->ObjKindOf) {
case LIGHT:
return ( ((Node*) My3D_Light_ResolvePtr(hNode));
break;
case GEOMETRY:
return ( ((Node*) My3D_Geometry_ResolvePtr(hNode));
break;
default:
assert(0);
return (NULL);
break;
};
}
Obviously, this is much different from the implementations of My3D_xxxx_ResolvePtr for other interface classes-none of the other classes you have considered have subclasses.
Because you want your Java subclasses of Node to be able to stand in for methods requiring a Node, just like when Node was used in your C++ library, special mechanisms need to be built in your superclass interface implementation to handle this.
For instance, when you attach a Light
object to the World using
the method AttachNode(Node),
you use the properties of Light
as a subclass of Node to
enable it to stand in as a Node
to be
attached. If you remember the implementation of the My3D_World_AttachNode
for the World interface,
you used the following statement to call AttachNode:
My3D_World_ResolvePtr(hthis)->AttachNode(My3D_Node_ResolvePtr(hNode));
Because there are two subclasses of Node-Light and Geometry-the hNode handle passed to My3D_Node_ResolvePtr could be either a Light or a Geometry object. This is the reason for the switch statement in the My3D_xxxx_ResolvePtr function implementation-you need to know to which object this handle refers. With the object determined, you can then call the My3D_xxxx_ResolvePtr function for the appropriate object to retrieve the pointer.
With this in mind, case statements were added for both the Light and Geometry objects in the ResolvePtr function. The LIGHT and GEOMETRY constants can be anything, but they must be different. This Java interface defines them in a global header file (see Listing 32.36), which also contains all the public interface functions for each of the objects for linking.
Listing 32.36. Global header file.
#define WORLD 1
#define GEOMETRY 2
#define LIGHT 3
Node* My3D_Node_ResolvePtr(struct HMy3D_Node *hNode);
Node* My3D_Node_FindHandle (Node* CPPNode);
void My3D_Node_DecRefCntr(struct HMy3D_Node *hNode);
void My3D_Node_IncRefCntr(struct HMy3D_Node *hNode);
Unfortunately, to do the inverse and find a Java Node handle given a Node* requires that you modify the C++ subclass to tell you what class it represents. To do this in your graphics library, you add to the C++ class a method int KindOf() that returns the object type as defined in the global header file in Listing 32.36. With this information, the class can chain the request of My3D_xxxx_FindHandle down to the correct class interface code, as shown in Listing 32.37.
Listing 32.37. Node C++ FindHandle implementation.
/*
** Title: FindHandle()
** Function: Takes a pointer to a World class instantiation
** and looks up the associated Java handle to the
** object.
*/
struct HMy3D_Node* My3D_Node_FindHandle (Node* CPPNode) {
switch (CPPNode->KindOf()) {
case LIGHT:
return (My3D_Light_FindHandle(CPPNode));
break;
case GEOMETRY:
return (My3D_Geometry_FindHandle(CPPNode));
break;
default:
assert(0);
return (NULL);
break;
};
}
In the same manner, the My3D_Node_IncRefCntr and My3D_Node_DecRefCntr interface functions also need this mechanism. Their implementation is shown in Listing 32.38.
Listing 32.38. Node C++ IncRefCntr and DecRefCntr implementation.
/*
** Title: IncRefCntr()
** Function: Performs automatic memory management. Passes the call
** from this function to wrappers for the subclass of
** Node.
*/
void My3D_Node_IncRefCntr (struct HMy3D_Node* hNode) {
long hNode;
switch (unhand(hNode)->ObjKindOf) {
case LIGHT:
My3D_Light_IncRefCntr(hNode);
break;
case GEOMETRY:
My3D_Geometry_IncRefCntr(hNode);
break;
default:
assert(0);
break;
};
}
/*
** Title: DecRefCntr()
** Function: Performs automatic memory management. Passes the call
** from this function to wrappers for the subclass of
** Node.
*/
void My3D_Node_DecRefCntr (struct HMy3D_Node* hNode) {
long hNode;
switch (unhand(hNode)->ObjKindOf) {
case LIGHT:
My3D_Light_DecRefCntr(hNode);
break;
case GEOMETRY:
My3D_Geometry_DecRefCntr(hNode);
break;
default:
assert(0);
break;
};
}
The one final component that you need to integrate into your interface is automatic handling of Java objects that move out of scope. When a Java object moves out of scope, the method finalize is called, so the behavior you want in your interface code is to adjust the reference counters appropriately. You accomplish this by simply linking the call to My3D_xxxx_finalize with a call to My3D_xxxx_DecRefCntr(), as shown in Listing 32.39 for the World class. All Java classes that link to C++ classes that can be instantiated should include interface code for finalize like this (that is, the Node class should be the only class that doesn't include code like this in the ones that you have examined).
Listing 32.39. Linking the call to My3D_World_finalize.
void My3D_World_finalize(struct HMy3D_World* hthis) {
My3D_World_DecRefCntr(unhand(hthis)->hCPPObj);
}
This covers most of what you need to know to build a Java interface to a C++ library! A sample program using the My3D interface library is shown in Listing 32.40. Because this simple 3D library doesn't have enough functionality to render the scene, you can't run the program; but note how the program has the look and feel of a normal Java program, meeting the design goals at the beginning of the section.
Listing 32.40. Sample3DProg implementation.
import My3D;
class Sample3DProg {
void main(String argv[]) {
World theWorld = new World();
PointF_t LightDir = new PointF_t();
LightDir.x = 0.0f;
LightDir.y = 0.0f;
LightDir.z = 1.0f;
PointF_t LightLoc = new PointFW_t();
LightDir.x = 0.0f;
LightDir.y = 0.0f;
LightDir.z = 10.0f;
LightDir.w = 1.0f;
Light theLight = new Light();
theLight.SetLocation(LightLoc);
theLight.SetDirection(LightDir);
}
}
In this section, you cover some miscellaneous tricks and tips that can be used to make your native library implementations more robust. First, you deal with the problem of a missing native library on your target machine and how to handle this event gracefully. Then, you deal with interfacing to C++ libraries that include templates, multiple inheritance, and operator overloading-features that are not supported in the Java implementation.
As you may have already found out in developing a native library for an applet, any native shared library needed must be installed on the target system by the user manually (without the help of the browser) before an applet can run on that system. Unfortunately, the absence of your native library may not be detected until an unfortunate user tries to read a Web page that requires it. Without special functionality, when this happens, the applet displays an error message in the status line of Netscape and dies-not the ideal reaction. Worse yet, the user has no idea of where to look for your native library.
Wouldn't it be great if you could develop some prologue code that checked to see whether your native library existed before starting the applet that used it? If the library did exist, the applet could begin execution without delay. If it didn't, the prologue code would display an informative error message and offer to point the browser to the URL where the native library could be downloaded.
In this section, you learn how to develop the applet prologue code necessary to do just this. Developed as a subclass of applet, it would ideally be given to the developer using your native library to use in place of the java.awt.Applet class. Because all applet code is sent over the network when the WWW page containing it is accessed, it isn't necessary to have your library installed to run the prologue code.
This applet prologue code, NativeApplet, is designed as a subclass of Applet (see Listing 32.41). This subclass detects a missing SHARED LIBRARY by trapping the UnsatisfiedLinkError error thrown by the loadLibrary command in the InterfaceObject you considered earlier. It also tries to detect missing class files by trying to instantiate a test InterfaceObject object in its constructor. You use this extension to retrofit the My3D graphics library and the sample My3D applet to show how this code can be used to provide the needed functionality.
Listing 32.41. NativeApplet implementation.
package My3D;
import My3D.*;
import java.applet.*;
import java.util.*;
import java.lang.*;
import java.io.*;
import java.awt.*;
public class NativeApplet extends Applet {
SendOk TxfrOkDlg;
Frame NativeFrame;
AppletContext AC;
public NativeApplet() {
LibNotFound = 0;
try {
InterfaceObject TrapDLLMissing = new InterfaceObject();
} catch (NoClassDefFoundError e) {
LibNotFound = 1;
}
if (LibNotFound == 0) {
if (InterfaceObject.DLLLoaded == 0) {
LibNotFound = 1;
}
}
}
public void NativeInit() {
if (LibNotFound == 1) {
NativeFrame = new Frame();
TxfrOkDlg = new SendOk(NativeFrame, getAppletContext());
TxfrOkDlg.show();
}
}
public void NativeStop() {
if (LibNotFound == 1) {
TxfrOkDlg.hide();
TxfrOkDlg.dispose();
}
}
protected int LibNotFound;
}
The NativeApplet class works by trying to instantiate an InterfaceObject within its constructor. If the class InterfaceObject can't be found, the attempt to instantiate it will throw a NoClassDefFoundError exception, which is trapped by the NativeApplet constructor, flagging the variable LibNotFound.
You have to change the InterfaceObject class in order to handle a missing native library shared library file. The necessary changes are shown in Listing 32.42. A missing native library shared library throws an UnsatisfiedLinkError exception, which your new InterfaceObject handler traps and uses to set the static variable DLLLoaded. This is used by the NativeApplet class to check for a "shared library not loaded" error.
Listing 32.42. Changes to InterfaceObject.
static {
try {
System.load("/java/classes/SolidCoffee/java3dw.dll");
DLLLoaded = 1;
} catch (UnsatisfiedLinkError e) {
DLLLoaded = 0;
}
static public int DLLLoaded;
}
To handle the case when either the native library shared library file is missing or the class files for the library are missing, you write a class called SendOk that pops up a dialog to inform the user that at least one of the components is missing. The class then gives the user the option of pointing the browser to the Web site containing the installation package for your library. The implementation of the SendOk class is shown in Listing 32.43.
Listing 32.43. SendOK class implementation.
import java.awt.*;
import java.applet.*;
import java.io.*;
import java.net.*;
public class SendOk extends Dialog {
AppletContext AC;
public SendOk(Frame parent, AppletContext InAC) {
super(parent, true);
AC = InAC;
setBackground(Color.gray);
setLayout(new BorderLayout());
setTitle("My3D package not found!");
Panel p = new Panel();
p.add(new Button("Yes!"));
p.add(new Button("No."));
add("South", p);
resize(250,150);
}
public boolean action(Event evt, Object arg) {
URL home;
if (arg.toString().compareTo("Yes!") == 0) {
try {
home = new URL("http://www.mystartup.com");
AC.showDocument(home);
hide();
dispose();
} catch (MalformedURLException e) {
System.out.println("SendOk::action(): malformed URL");
} catch (IOException e) {
System.out.println("SendOk::action(): io exception");
} catch (Exception e) {
System.out.println("SendOk::action(): other exception:" +
e.toString());
e.printStackTrace();
}
return true;
}
if (arg.toString().compareTo("No.") == 0) {
hide();
dispose();
return true;
}
return false;
}
public void paint(Graphics g) {
g.setColor(Color.white);
g.drawString("Your computer doesn't have the My3D library", 20, 20);
g.drawString("installed, which is needed for this applet.", 20, 35);
g.drawString("Should I point your browser to the", 20, 55);
g.drawString("My3D home page for you?", 20, 70);
}
}
That's all there is to it! This mechanism makes it easier for end users to find and install your native library package. This is important! With the influx of less experienced users to the Internet, only a small percentage of them will have the expertise to interpret the always-cryptic error messages that display in the status line of their browser screen.
You may have already noticed that Java doesn't implement the full set of object-oriented features that C++ does. This can be problematic when you are trying to interface with a C++ library, because it will make providing the look and feel with your set of parallel Java libraries the same as your C++ libraries much more difficult. In this subsection, you tackle the problems of interfacing your library with C++ classes that contain such features.
The first shortcoming that Java has is the absence of templates. Templates are a C++ feature that enable you to substitute any type or class for designator labels defined within the definition of the class. Unfortunately, there really isn't any ideal solution for this shortcoming. As a workaround, you can simply create a Java class for all the types that might reasonably be expected to be used with the native class. Consider Listing 32.44, a C++ collection template class.
Listing 32.44. The C++ collection template class.
template <class T>
class Collection<T> {
public:
Collection(int initialLength = 0, int allocLength);
Collection(const Collection<T>& src);
~Collection(void);
int GetLength(void) const;
void Resize (int NewSize);
void AddElem (const T& elem);
void DelElem (const int elemNum);
private:
T* CollArray;
int CurrLength;
int AllocLength;
};
To interface a Java class to this Collection class for the Java String type, you create a Java interface class whose implementation constructor includes the C++ class template filled with a char * to hold the String's contents. To make the translation more mnemonic, you also suffix the name of the Java interface class to represent this. The Java interface class for Strings is shown in Listing 32.45.
Listing 32.45. A Java interface class for CollectionStrings.
class CollectionString {
private native void constructorA(int initialLength,
int allocLength);
public CollectionString(int initialLength, int allocLength) {
constructorA(initialLength, allocLength);
}
private native void constructorB(CollectionString src);
public CollectionString(CollectionString src) {
constructorB(src);
}
public int GetLength(void) const;
public void Resize (int NewSize);
public void AddElem (String elem);
public void DelElem (int elemNum);
public String GetElem (int elemNum);
}
Listing 32.46 shows the C++ implementation of the constructor
for this class. The two important points that you should notice
in this constructor is that you link this implementation of the
CollectionString object to
a constructor that constructs a Collection<char
*> object, and that other than this the C++ implementation
of the constructor is the same as the constructors you considered
in interfacing to the My3D class.
Listing 32.46. Collection<char*> interface constructor.
long My3D_CollectionString_AllocLength = 0;
#define INITIAL_My3D_CollectionString_ALLOC 2
Collection<char*>** My3D_CollectionString_ObjPtr = NULL;
long* My3D_CollectionString_ObjRef = NULL;
/*
** Title: constructor()
** Function: Instantiates the C++ Collection<char*> class
** and sets the handle in the Java object.
*/
void My3D_CollectionString_constructor(
struct HMy3D_CollectionString *jthis) {
// Check to see if the initial allocation for the
// Collection<char*> class has been done yet. If not, allocate
// the necessary data structures.
if (My3D_CollectionString_AllocLength == 0) {
My3D_CollectionString_AllocLength = My3D_CollectionString_Resize(INITIAL_My3D_CollectionString_ALLOC);
}
// Search for an empty position (empty position == NULL).
long pos;
for ( pos=0;
(pos != My3D_CollectionString_AllocLength) &&
(My3D_CollectionString_ObjPtr[pos] != NULL);
pos++ )
;
if (pos == My3D_CollectionString_AllocLength) {
// All allocated positions are full.
// So use exponential allocation to create some more.
My3D_CollectionString_AllocLength =
My3D_CollectionString_Resize(My3D_CollectionString_AllocLength*2);
}
My3D_CollectionString_ObjPtr[pos] = new Collection<char*>();
// Stub for handling out of memory condition.
// Handle as desired in your implementation.
assert (My3D_CollectionString_ObjPtr[pos] != NULL);
// Increment Reference counter.
My3D_CollectionString_IncRefCntr(pos);
// Store handle (== position in array) for this
// object.
unhand(jthis)->hCPPObj = pos;
}
With the constructor built, the rest of the implementation of the Collection class for Strings is straightforward. It follows exactly the development you followed in the previous section on interfacing to C++ libraries. Once the Collection class interface has been developed around the String object, you can make Collection classes for any type by simply searching and replacing String with the desired type.
What happens if the Collection class didn't include the GetElem accessor, but instead overloaded the [] operator to provide access? Java doesn't include operator overloading, so the only course of action to you is to add a GetElem type accessor to your Java Collection class that maps to a call to the [] operator in your C++ implementation.
The final object-oriented shortcoming to handle in Java is the lack of multiple inheritance. You've already worked with C++ class libraries in this chapter that deal with single inheritance, where the subclass derives its functionality from only one superclass. You haven't seen how to handle multiple inheritance derived subclasses, however. In this section, you see how to handle multiple inheritance by using interfaces.
Because you've been considering an interface with a 3D graphics library in this chapter, let's consider a 3D library implementation that uses multiple inheritance. Shown in Listing 32.47 is the C++ class Location, which contains all the data and accessors for managing a positional object for a hypothetical 3D graphics library.
Listing 32.47. Location C++ class definition.
class Location {
public:
void SetLocation (PointFW_t theLoc) {Location = theLoc};
PointFW_t GetLocation () {return(Location)};
private:
PointFW_t Location;
}
Listing 32.48 defines a Direction mix-in that adds Direction to any object it is applied to using inheritance.
Listing 32.48. Direction C++ class definition.
class Direction {
public:
void SetDirection (PointF_t theDir) {Direction = theDir};
PointF_t GetDirection () {return(Direction)};
private:
PointF_t Direction;
}
Finally, Listing 32.49 defines a Color mix-in that adds a color property to any object.
Listing 32.49. Color C++ class definition.
class Color {
public:
void SetColor (ColorF_t theColor) {Color = theColor};
ColorF_t GetColor () {return(Color)};
private:
ColorF_t Color;
}
Tying these classes together is the Light class, shown in Listing 32.50, which uses all three of the mix-ins to provide Location, Direction, and Color information.
Listing 32.50. Light C++ class definition.
class Light: public Direction, Location, Color {
public:
void SetAttenuation (float a);
float GetAttenuation ();
private:
float Atten;
}
Unfortunately, as with templates in the previous chapter, there is really no good solution to the multiple inheritance problem. If at least all but one of the superclasses being inherited by the class are abstract, you can use a Java interface to simulate multiple inheritance.
Shown in Listings 32.51, 32.52, and 32.53 are Java interfaces that do just this for the Location, Direction, and Color classes, respectively. These classes cannot be instantiated by themselves, but they can be mixed in with other interface classes to provide structure for the subclass.
Listing 32.51. Location Java class definition.
public interface Location {
public void SetLocation (PointFW_t theLocation);
public PointFW_t GetLocation();
public PointFW_t Location=null;
}
Listing 32.52. Direction Java class definition.
public interface Direction {
public void SetDirection (PointF_t theDir);
public PointF_t GetDirection();
public PointF_t Direction=null;
}
Listing 32.53. Color Java class definition.
public interface Color {
public void SetColor (ColorF_t theColor);
public ColorF_t GetColor();
public ColorF_t Color=null;
}
Shown in Listing 32.54 is the Java implementation of the Light class. Note that all the methods defined in the Direction, Location, and Color must be implemented in the Light class, but the interface still enforces the common interface between all the objects that contain one or more of the interface classes.
Listing 32.54. Light Java class definition.
public class Light implements Direction, Location, Color {
public native void SetDirection(PointF_t theDir);
public native PointF_t GetDirection();
public void SetLocation(PointFW_t theLoc);
public PointFW_t GetLocation();
public void SetColor(ColorF_t theColor);
public ColorF_t GetColor();
}
You've covered a lot of ground in this chapter! Take some time to let it soak in before you continue. You learned how to interface to legacy C and C++ libraries, and you considered the trade-offs in the different options for solving this. You also saw how to overcome some of the shortcomings of Java's object-oriented interface, using different techniques to emulate C++'s templates, multiple inheritance, and operator overloading.