All Categories :
Java
Chapter 33
Integrating Native Code
by Tim Park
CONTENTS
This chapter explains how to use the native method interface to
speed up your Java programs and to interface with legacy C and
C++ libraries. Native methods are the mechanism in Java
that allows you to interface with programs written in C. Legacy
C and C++ libraries are any libraries that were developed
first for the C/C++ platform, but that you now want to interface
with in Java. We'll first look at three examples of how to use
the native method interface, working through the basics of Java
native method programming. After learning the basics, we will
then use a simple 3D graphics library to show how you can use
the native method interface as a bridge to legacy C++ code.
The first example uses the Triangle
class to explain the process of developing a native method and
the files the Java Developers Kit (JDK) generates to link together
Java and C. Then you discover how to access member variables from
within a native method for that class. Finally, you see how to
pass back a simple type using the return stack.
The second example uses the SortedList
class to explore passing strings to and from native methods. You
also see first-hand how much more complicated a native method
can be when you do memory allocation in C rather than as a member
variable of Java.
A common application of native methods is to improve the speed
with which Java can process arrays of data. The third example
explores the mechanisms necessary for accessing arrays from within
a native method.
In the second part of this chapter, you will look at My3D,
a C++ graphics library. The second part of the chapter also helps
you work through the steps necessary to build a Java interface
library to My3D.
Listing 33.1 gives you a first look at a Java class containing
native methods. This class encapsulates the base and height of
a triangle and contains a native method named ComputeArea()
that calculates the area of the triangle.
Listing 33.1. Triangle.java
contains a native method called ComputeArea().
public class Triangle {
public void SetBase(float fInBase) {
fBase = fInBase;
}
public void SetHeight(float fInHeight) {
fHeight = fInHeight;
}
public native float ComputeArea();
// Load the native routines.
static {
System.loadLibrary("Triangle");
}
float fBase;
float fHeight;
}
As you can see, the definition for ComputeArea()
is only slightly different from the definition of a normal method.
The keyword native is added
just after the scope of the method and just before the return
type. This tells the javac
compiler and the Java interpreter that they should look for the
function body in a dynamically linked library (a DLL, in a Microsoft
Windows environment, which is named Triangle.DLL)
that is loaded using loadLibrary(),
a static method in the Java system package. The loadLibrary
definition directly following the ComputeArea()
definition specifies where this dynamically loaded library can
be found.
Note |
The Triangle class shown in Figure 33.1 and all the files you use in the remainder of this chapter can be found on the CD-ROM that accompanies this book.
|
Compiling the Java Class
To build your class, copy the entire \WIN95NT4\SOURCE\CHAP33\TRIANGLE
directory from the CD-ROM that accompanies this book into \java\classes\Triangle
on your own computer. Macintosh users will find the code in \SOURCE\CHAP33\TRIANGLE;
Windows NT 3.51 users must either install the source code to their
hard drives or select the files from the zipped source code on
the CD-ROM. From \java\classes\Triangle,
compile the Triangle class
using javac just as you normally
would:
C:\java\classes\Triangle> javac Triangle.java
In a normal Java program, this statement would do it-your class
would be ready. In a native application, however, you have to
generate or supply three more source files to tie everything together.
Using javah
to Generate the Header File
The first file you need to generate for a native application is
a header file for the Java Triangle
class (see Listing 33.2). This header file gives the native C
code routine a layout of how data is arranged within your Java
class. It also provides a prototype of how the methods from your
object-oriented naming-space class files translate into C's flat
naming space.
To generate the stub header file from the Triangle.class
file, execute javah in the
C:\java\classes\native directory:
C:\java\classes\Triangle> javah Triangle.java
Listing 33.2. The Triangle.h
file (generated by javah).
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <native.h>
/* Header for class Triangle */
#ifndef _Included_Triangle
#define _Included_Triangle
typedef struct ClassTriangle {
float fBase;
float fHeight;
} ClassTriangle;
HandleTo(Triangle);
#ifdef __cplusplus
extern "C" {
#endif
__declspec(dllexport) float Triangle_ComputeArea(struct HTriangle *);
#ifdef __cplusplus
}
#endif
#endif
The header file defines a new type called ClassTriangle.
This structure enables access to the internal variables of the
Triangle class. Each intrinsic
type (types not defined by the Java class library or the developer)
in Java has a corresponding type in Java. Table 33.1 shows this
correlation (for Windows 95 and Microsoft development platforms;
other combinations may have slight differences).
Table 33.1. Java and C type correspondence.
Java Type | C Type
|
float |
float |
double |
double |
int |
long |
short |
int |
long |
long |
boolean
| long |
byte |
char |
For developer-defined objects or Java library objects (for example,
String objects), there can
be cases where there are no one-to-one type correspondences with
C. This can be a major headache in Java-but we tackle this problem
in the second native method interface example.
The second part of the javah-generated
header file contains the prototypes for the native functions defined
in the Java class. For the Triangle
class, there is only one prototype: the ComputeArea()
method. The return type is float
(as expected from the type-translation chart in Table 33.1), but
the function contains an unexpected input parameter of type struct
Htriangle *. This parameter is a handle to the instance
of the Triangle class that
called the native function. This handle lets you access the Triangle
class variables through the class's ClassTriangle
structure. You see how to access these variables later in this
chapter when you implement the native function in C.
Using javah -stubs
to Generate a Stub File
Your next task in implementing a native method is to build a stub
file from Java's class file representation of the Triangle
class. This stub file is responsible for finding the parameters
and return values on Java's call stack and translating them into
parameters for the native C method. The Java interpreter calls
this stub, which in turn calls the native method from the DLL
you loaded with the System.loadLibrary()
call a few sections earlier.
To create the stub file, type the following at the command line:
C:\java\classes\Triangle> javah -stubs Triangle
This statement creates the file Triangle.c
(see Listing 33.3).
Listing 33.3. The Triangle.c
file generated by javah -stubs.
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <StubPreamble.h>
/* Stubs for class Triangle */
/* SYMBOL: "Triangle/ComputeArea()F", Java_Triangle_ComputeArea_stub */
__declspec(dllexport) stack_item *Java_Triangle_ComputeArea_stub(stack_item *_P_,struct execenv *_EE_) {
extern float Triangle_ComputeArea(void *);
_P_[0].f = Triangle_ComputeArea(_P_[0].p);
return _P_ + 1;
}
Developing an Implementation C File
This is a lot of work for one puny native method. Fortunately,
you'll use a makefile to automate your work in future sessions.
The worst is over-you're ready to build an implementation of the
native function.
For the Java interpreter to find the native function, the interpreter
has to match the prototype in the Triangle.h
file type for type.
Here is the prototype from the generated header file:
__declspec(dllexport) float Triangle_ComputeArea(struct HTriangle *);
In the implementation file, you must match exactly everything
from the float return type
to the right.
As shown in Listing 33.3, the implementation file must also include
two header files: StubPreamble.h
and Triangle.h. Triangle.h
is the file you generated with javah
in the previous sections. StubPreamble.h
is a Java library that includes definitions needed to enable you
to access your Java data parameters and use certain Java C interpreter
calls.
The final building block you need to implement the native ComputeArea()
function is a way of accessing the class variables from within
the native function. You accomplish this using the unhand
macro provided by the StubPreamble.h
header file. The unhand macro
takes a pointer to a handle to a class, such as struct
Htriangle*, and returns a pointer to a ClassClass
structure, such as the ClassTriangle
structure described previously. As you may remember, this structure
contains the representation of the variables in the Java class.
Caution |
Because static variables do not belong to any one instantiation of a Java class, you cannot view or modify them from a native function in C. If you need a static variable that you can modify from your native method, define it in C instead and create accessor methods in the Java class to access it.
|
To access the value of the fBase
class variable, use the following syntax:
unhand(hthis)->fBase
By doing this for fHeight
as well, you can compute the area of the triangle and return it
to the Java interpreter and your Java program, as shown in Listing
33.4.
Listing 33.4. TriangleImp.c.
extern "C" {
#include <StubPreamble.h>
#include "Triangle.h"
float Triangle_ComputeArea(struct HTriangle *hthis) {
return(0.5f * unhand(hthis)->fBase * unhand(hthis)->fHeight);
}
}
Building the Triangle DLL
With the four implementation files for the native-method-containing
Java class completed, you now need only to compile two C source
files and link them with the Java library to form the Triangle
DLL file.
This discussion uses command lines in Visual C++ 4.0 for Windows
95 to develop the native functions. Because Windows 95 is the
most prevalent Java platform in use today, you'll learn how to
link the library by using Visual C++. The instructions for compiling
in UNIX are very similar. Consult the manual supplied with your
JDK for instructions.
Before you start your build, you have to add a few environmental
variables so that the Visual C++ compiler can find your tools
and Java/Netscape can find your native DLL files. Add the following
lines to your autoexec.bat
file (use the paths for the standard directories):
SET LIB=\msdev\lib;\java\lib
SET INCLUDE=\java\include;\java\include\win32;\msdev\include
SET CLASSPATH=\java\classes;.;C:\netscape20\Program\java\classes
With these changes made, reexecute your autoexec.bat
file by rebooting your computer. Then move back to your Java source
directory and compile the implementation of the native Triangle
class with the following command line. (Note that cl
is the Microsoft Visual C++ command-line compiler. For other development
platforms, substitute the equivalent command.)
C:\java\classes\Triangle> cl /c W3dTriangleImp.c
Likewise, compile the stub file with this command line:
C:\java\classes\Triangle> cl /c W3dTriangleImp.c
Finally, link these two OBJ files with the Java interpreter library
(javai.lib) to form the finished
DLL file:
cl Triangle.obj TriangleImp.obj -FeTriangle.dll -MD -LD javai.lib
Listing 33.5 shows a skeleton makefile used to build Java native
applications. It includes all the commands and dependencies necessary
to build a Triangle DLL. By replicating the dependency section
and changing the names, you can reuse this makefile to build your
own native functions.
Listing 33.5. Triangle.mak.
OBJS = Triangle.obj TriangleImp.obj
LIBS = javai.lib
COMPFLAGS = /c /MLd /Gm /Od /Zi
Triangle.dll: $(OBJS)
cl $(OBJS) -FeTriangle.dll -MD -LD $(LIBS)
# Build Triangle class
Triangle.class: Triangle.java
javac Triangle.java
Triangle.h: Triangle.class
javah Triangle
Triangle.obj: TriangleImp.cpp Triangle.h
cl $(COMPFLAGS) W3dTriangleImp.c
Triangle.c: Triangle.class
javah -o Triangle.c -stubs Triangle
Triangle.obj: Triangle.c
cl $(COMPFLAGS) Triangle.c
Let's build a test class to demonstrate the new Triangle
class in action. Listing 33.6 shows a test application that uses
the Triangle class. After
building it with javac, use
the Java interpreter to run
the application.
Listing 33.6. TestTriangle.java.
public class TestTriangle {
public static void main(String argv[]) {
Triangle theTri;
theTri = new Triangle();
theTri.SetBase(2.0f);
theTri.SetHeight(6.0f);
System.out.println("The Area of the Triangle is"+theTri.ComputeArea());
}
}
Following is the output of the TestTriangle
execution:
C:\java\classes\Triangle> java TestTriangle
The Area of the Triangle is 6.0
Hooray! Your first native method works correctly.
Now let's look at a class that has native methods that accept
a Java class as one of its
parameters.
The class SortedList, shown
in Listing 33.7, maintains a sorted list of strings. Because sorting
is a compute-intensive task, let's write the sorting algorithm
as a native method. Native methods are good at compute-intensive
tasks because C code runs as compiled instructions for the underlying
hardware; Java, on the other hand, runs virtual machine instructions
that are very far from the underlying hardware.
Listing 33.7. SortedList.java.
public class SortedList {
public native void constructorA();
public SortedList() {
constructorA();
}
public native void constructorB(int nInitialAllocation);
public SortedList(int nInitialAllocation) {
constructorB(nInitialAllocation);
}
public native void AddString(String szString);
public native String GetString(int nIndex);
public native int HowMany();
static {
System.loadLibrary("SortedList");
}
}
The first thing you notice about the native SortedList
class is that there are two Java constructors that immediately
call native C constructor implementations. This is done for two
reasons. First, Java doesn't allow constructors to be native,
so for classes that require native constructors, you must wrap
this call to the native function inside the Java constructor.
Secondly, native functions cannot be overloaded within Java because
all C function names must be unique and cannot depend on type,
and because the process for converting a Java method to a C function
name in Java always follows Package_Class_Method.
For Java classes with overloaded constructors, therefore, you
must overload the constructors in Java and then call the corresponding
native methods. Not pretty, but the end user of your Java class
won't notice the difference.
The next thing you should note about the SortedList
class is that the class takes and returns a String
from its AddString() and
GetString() methods, respectively.
This arrangement is different than what we did in the first example
because here you are dealing with a Java class, String,
that has no direct counterpart type in C/C++. (Although (char
*) may seem like a direct counterpart, it isn't because
it doesn't encapsulate a string's length.)
The C/C++ implementation of the SortedList
implementation is shown in Listing 33.8. The compiler directive
extern "C" { in
the first line tells the compiler that the definitions of functions
and variables contained within its braces should be defined internally
using a C-style definition rather than a C++ style. The difference
between these two definitions is that the C++-style definition
includes information about the types passed to and returned from
the function; the C-style definition contains only the function
name. If you leave out the extern "C"
{ directive, all the functions are defined in the
C++ style by default. The linker then, looking for a definition
to match the C-style definitions of the stub file, will be unable
to link the functions together correctly because they will be
defined using the C++ style. Also notice that the extern
"C" { directive surrounds the Java include
files. These include files
also have prototypes for Java interpreter functions that must
be defined using the C-style definition.
From the length of the implementation file, you should note the
complexity of implementing data management for your Java native
class in C. Because C isn't object oriented, special wrapper code
must be developed for any native Java class that manages its data
in C.
Tip |
Store your data in Java and use unhand() to access it whenever it yields acceptable performance. Doing so can cut down your development time and simplify your code considerably because it will be unnecessary to write wrapper code to manage the instantiations of your Java class.
|
You can't follow the preceding tip for the SortedList
implementation, because the overhead from converting strings back
and forth from Java and C will choke the sorting routine and rob
you of much of the C performance advantage. By moving the data
management to C, you only have to convert strings in the AddString()
and GetString() methods and
not in the performance-critical BubbleSort()
function.
Now let's focus on the interface for data from and to Java, starting
with the AddString() native
method.
Implementing AddString()
The AddString() method takes
a String and adds it to the
list maintained by the given object. The prototype for the native
C function looks like this:
void SortedList_AddString(struct HSortedList *hthis,
struct Hjava_lang_String *AddString);
This is pretty much as you expect: the first parameter is the
handle to the Java SortedList
object and the second is the handle to the Java string being passed.
Notice that the structure name follows the package_package_..._class
nomenclature.
Java strings are not passed by value, as were all the types you
considered previously. Instead, a handle to the Java string is
passed. If you haven't run into them before, handles can be likened
to a license plate on a car-they uniquely identify an object.
Handles are used to avoid the overhead in passing large objects
to functions and to provide security that doesn't exist if a pointer
is given to reference the object. With a pointer, you can do almost
anything to the object. With a handle, you are constrained to
the API given to you, providing as much security as desired by
the API designer.
Because Java strings and C strings are stored differently, you
must use the makeCString()
interpreter API call to convert your string first. makeCString()
converts the String referenced
by a handle and allocates enough memory for the converted string,
for which it returns a (char *)
pointer. Here is a use of makeCString(),
copied from the AddString()
function:
//Add String to the end of the List specified by hList.
lLists[hList][nStringsUsed[hList]++] = makeCString(AddString);
You should include javaString.h
in your #include section
whenever you use the makeCString()
function or the makeJavaString()
function described in the next section. This include
file defines the prototypes for these two interpreter functions.
Implementing GetString()
As you may expect, the GetString()
native function has a similar conversion need. To return the String
referenced by the passed index, Java has to convert the C string
stored in the C string table back into a handle to a Java String.
The analogous Java interpreter function call needed to convert
C (char *) variables into
strings is called makeJavaString(char*
theString, int strlength). makeJavaString()
takes a char * and a string
length as an int, instan-tiates
a String variable in Java
with that value, and returns a handle to that string (struct
Hjava_lang_String *). The GetString()
native function uses makeJavaString
to return the [nIndex]th
String in the [hList]th
List as follows:
return (makeJavaString(lLists[hList][nIndex],strlen(lLists[hList][nIndex])));
Note that the GetString()
function returns a handle to a Java string (struct
Hjava_lang_String*) just as the AddString()
native function accepted one.
The complete SortedList implementation
is shown in Listing 33.8. This listing shows how all the parts
described in the previous sections fit together.
Listing 33.8. SortedListImp.cpp.
extern "C" {
#include <StubPreamble.h>
#include <javaString.h>
#include "SortedList.h"
#include <string.h>
/* nLists is a long that contains the number of lists that lString
has been allocated to contain. */
long nLists;
long nListsAllocated = 0;
long nListsUsed = 0;
long *nStringsAllocated;
char ***lLists;
long *nStringsUsed;
long SortedList_ResizeNumLists(long nNewAllocation) {
char*** NewlLists;
long* NewnStringsAllocated;
long* NewnStringsUsed;
NewlLists = new char** [nNewAllocation];
NewnStringsAllocated = new long [nNewAllocation];
NewnStringsUsed = new long [nNewAllocation];
long i;
for (i=0; (i < nListsAllocated) && (i < nNewAllocation); i++) {
NewlLists[i] = lLists[i];
NewnStringsAllocated[i] = nStringsAllocated[i];
NewnStringsUsed[i] = nStringsUsed[i];
}
for (; (i < nNewAllocation); i++) {
NewlLists[i] = NULL;
NewnStringsAllocated[i] = 0;
NewnStringsUsed[i] = 0;
}
delete lLists;
delete nStringsAllocated;
delete nStringsUsed;
lLists = NewlLists;
nStringsAllocated = NewnStringsAllocated;
nStringsUsed = NewnStringsUsed;
return (nNewAllocation);
}
long SortedList_ResizeNumStrings(long hList, long nNewAllocation) {
char** NewlStrings = new char* [nNewAllocation];
long i;
for (i=0; (i < nListsAllocated) && (i < nNewAllocation); i++) {
NewlStrings[i] = lLists[hList][i];
}
for (; (i < nNewAllocation); i++) {
NewlStrings[i] = NULL;
}
delete lLists[hList];
lLists[hList] = NewlStrings;
return (nNewAllocation);
}
void SortedList_constructorA(struct HSortedList *hthis) {
if (nListsAllocated == 0)
nListsAllocated = SortedList_ResizeNumLists(1);
long i;
int done = FALSE;
if (nListsUsed == nListsAllocated)
nListsAllocated = SortedList_ResizeNumLists(nListsAllocated*2);
nStringsAllocated[nListsUsed] = 0;
nStringsUsed[nListsUsed] = 0;
unhand(hthis)->hList = nListsUsed++;
}
void SortedList_constructorB(struct HSortedList *hthis,
long InitialAllocation) {
if (nListsAllocated == 0)
nListsAllocated = SortedList_ResizeNumLists(1);
long i;
int done = FALSE;
if (nListsUsed == nListsAllocated)
nListsAllocated = SortedList_ResizeNumLists(nListsAllocated*2);
nStringsUsed[nListsUsed] = 0;
nStringsAllocated[nListsUsed] = SortedList_ResizeNumStrings(nListsUsed,
InitialAllocation);
unhand(hthis)->hList = nListsUsed++;
}
void BubbleSort(char* lSortStrings[], int nElements) {
long i,j;
int changed=TRUE;
for (j=0; (j < nElements-1) && changed; j++) {
changed = FALSE;
for (i=0; i < nElements-1; i++) {
if (strcmp(lSortStrings[i], lSortStrings[i+1]) > 0) {
char* temp = lSortStrings[i];
lSortStrings[i] = lSortStrings[i+1];
lSortStrings[i+1] = temp;
changed = TRUE;
}
}
}
}
void SortedList_AddString(struct HSortedList *hthis,
struct Hjava_lang_String *AddString) {
int hList = unhand(hthis)->hList;
if (nStringsUsed[hList] == nStringsAllocated[hList])
nStringsAllocated[hList] =
SortedList_ResizeNumStrings(hList, nStringsAllocated[hList]*2);
lLists[hList][nStringsUsed[hList]++] = makeCString(AddString);
BubbleSort(lLists[hList], nStringsUsed[hList]);
}
struct Hjava_lang_String* SortedList_GetString(struct HSortedList *hthis,
long nIndex) {
int hList = unhand(hthis)->hList;
if (nIndex > nStringsUsed[hList]) {
return(NULL);
}
return (makeJavaString(lLists[hList][nIndex],strlen(lLists[hList][nIndex])));
}
long SortedList_HowMany(struct HSortedList* hthis) {
return(nStringsUsed[unhand(hthis)->hList]);
}
}
Building and Running a Class That Uses SortedList
A simple applet that uses the native SortedList
class is shown in Listing 33.9. SortPresidents
was written as an applet just to show that applets also can call
native functions. (There is one important caveat: The native code
DLL must be already installed on the client machine within a directory
contained in your PATH statement
before the applet is presented to Netscape or the applet viewer.)
SortPresidents takes a list
of presidents' names, sorts them based on last name, and prints
the results. Notice how SortedList
still "feels" like a Java class to the user. You should
strive for this feeling in your native Java class design.
Listing 33.9. SortPresidents.java.
import java.applet.*;
public class SortPresidents extends Applet {
public void init() {
thePresidents = new SortedList();
thePresidents.AddString("Washington, George");
thePresidents.AddString("Lincoln, Abraham");
thePresidents.AddString("Kennedy, John F");
thePresidents.AddString("Nixon, Richard");
thePresidents.AddString("Carter, Jimmy");
thePresidents.AddString("Reagan, Ronald");
thePresidents.AddString("Bush, George");
thePresidents.AddString("Clinton, Bill");
int i;
int nNames = thePresidents.HowMany();
System.out.println("There are "+nNames+" entries in our string list.");
for (i=0; i < nNames; i++)
System.out.println(thePresidents.GetString(i));
}
SortedList thePresidents;
}
To build your SortedList
class, copy the entire \WIN95NT4\SOURCE\CHAP33\SORTEDLIST
directory on the CD-ROM that accompanies this book into \java\classes\SortedList
on your machine. Macintosh users will find the code in \SOURCE\CHAP33\SORTEDLIST;
Windows NT 3.51 users must either install the source code to their
hard drives or select the files from the zipped source code on
the CD-ROM. From there, you use nmake
and the SortedList.mak makefile
to build the SortedList class.
This makefile looks identical to the one used to build the Triangle
class in the last section.
Your build of SortedList
should look something like this:
C:\java\classes\SortedList> nmake SortedList.mak
Microsoft (R) Program Maintenance Utility Version 1.60.5270
Copyright (c) Microsoft Corp 1988-1995. All rights reserved.
javac SortedList.java
javah SortedList
...
We also should compile our sample applet SortPresidents,
which uses the SortedList
class:
C:\java\classes\SortedList> javac SortPresidents.java
You are now ready to run the applet, as shown in Listing 33.10.
The HTML file RunIt.html
is bundled on the CD-ROM that accompanies this book; it contains
a link to this applet.
Listing 33.10. SortedList
build.
C:\java\classes\SortedList> appletviewer RunIt.html
There are 8 entries in our string list.
Bush, George
Carter, Jimmy
Clinton, Bill
Kennedy, John F
Lincoln, Abraham
Nixon, Richard
Reagan, Ronald
Washington, George
Because of the drastic performance advantage that C has in performing
array-based operations, it is very common to pass arrays into
a native routine. In this section, you explore how to pass and
receive arrays from a native method. To demonstrate passing arrays
to and from Java, the GradeBook
class and its C implementation are shown in Listings 33.11 and
33.12 (in the following section). The GradeBook
class implements a simple grade-tracking and average-computing
system.
The GradeBook class supports
only one instantiation (unlike the previous SortedList
class). GradeBook is designed
this way to simplify the implementation of GradeBook
and to remove the details present in the SortedList
class from the last section. A real application that wants to
use more than one GradeBook
class requires a rewrite of the class to support multiple instantiations.
Listing 33.11. GradeBook.java.
public class GradeBook {
public native void constructor(int nStudents, int nTests);
public GradeBook(int nStudents, int nTests) {
constructor(nStudents, nTests);
}
public native void NameStudents(String lStudents[]);
public native int AddTest(float lScores[]);
public native float GetTestAvg(int nTestNumber);
public native float GetStudentAvg(String szStudentName);
public native int HowManyTests();
static { System.loadLibrary("GradeBook"); }
}
Looking closer at the Java GradeBook
class, you should see two new data types that have not previously
been passed into a native method. First, the NameStudents()
method accepts a String array
that contains the list of students enrolled for the class. Second,
the AddTest() method accepts
an array of float for the
list of scores achieved by the respective students on a test.
In the implementation file, let's use this example to see how
you can decode Java arrays into C arrays.
This native class exploits C's array-operation performance advantage
over Java to improve the speed of searching for a student's records
and computing test and class averages.
Accessing a String Array
The implementation file for the GradeBook
class is shown in Listing 33.12. The file's general skeleton is
very similar to the last two classes you have considered.
Listing 33.12. GradeBookImp.cpp.
extern "C" {
#include <StubPreamble.h>
#include "GradeBook.h"
#include <string.h>
#include <javaString.h>
long nStudents;
char** lStudents;
long nTests;
float** lTests;
void GradeBook_constructor(struct HGradeBook *hthis, long nStudentsIN,
long nTotalTests) {
nStudents = nStudentsIN;
lStudents = new char* [nStudents];
nTests = 0;
lTests = new float* [nTotalTests];
}
long GradeBook_AddTest(struct HGradeBook *hthis,
struct HArrayOfFloat *lTestScoresIN) {
float* lJavaTestScores = (float *)(unhand(lTestScoresIN)->body);
float* lCTestScores = new float[nStudents];
int i;
for (i=0; i < nStudents; i++) {
lCTestScores[i] = lJavaTestScores[i];
}
lTests[nTests] = lCTestScores;
nTests++;
return (nTests);
}
void GradeBook_NameStudents(struct HGradeBook* hthis,
struct HArrayOfString* JavalStudents) {
struct Hjava_lang_String* hStudentName;
int i;
for (i=0; i < nStudents; i++) {
hStudentName = (struct Hjava_lang_String *)(unhand(JavalStudents)->body)[i];
lStudents[i] = makeCString(hStudentName);
}
}
float GradeBook_GetTestAvg(struct HGradeBook* hthis,
long nTestNumber) {
int i;
float* lTestScores = lTests[nTestNumber-1];
float fScoreAccum = 0;
for (i=0; i < nStudents; i++) {
fScoreAccum += lTestScores[i];
}
return (fScoreAccum/((float)nStudents));
}
float GradeBook_GetStudentAvg(struct HGradeBook* hthis,
struct Hjava_lang_String* hStudentName) {
char* szSearchStudent = makeCString(hStudentName);
long cStudentIndex, bDone;
for (cStudentIndex=0, bDone=FALSE; (cStudentIndex < nStudents) && !bDone;) {
if (strcmp(szSearchStudent, lStudents[cStudentIndex]) == 0) {
bDone = TRUE;
} else {
cStudentIndex++;
}
}
if (!bDone) // Student not found!
return (-1.0f);
for (long cTestNum=0, float fTestScoreAccum=0.0f; cTestNum < nTests;
cTestNum++) {
fTestScoreAccum += lTests[cTestNum][cStudentIndex];
}
float fTestAvg = fTestScoreAccum/((float)nTests);
return(fTestAvg);
}
long GradeBook_HowManyTests(struct HGradeBook* hthis) {
return(nTests);
}
The NameStudents() method
is responsible for associating the names of all the students in
the class with the test scores. This arrangement enables the user
of this class to pull a student's record using the student's name
as a query. Here is the NameStudents()
prototype:
void GradeBook_NameStudents(struct HGradeBook* hthis,
struct HArrayOfString* JavalStudents) {
Notice that Java has a special handle type for an array of String:
struct HArrayOfString*. This
handle contains only one element, body,
that you need to consider. The body
element is common to all struct HArrayOfObject*
handles in native method programming and is a pointer to a list
of handles (or, in the case of intrinsic Java types that have
C equivalents, the actual array of values). For struct
HArrayOfString, body
points to a list of String
handles that contain the names of all the students in the class.
Reading in the names of the students in the class is as easy as
writing a loop that grabs each student's string handle and converts
it to a C (char *) using
makeCString(), as described
in the previous section. To grab the individual string handle
and convert it, you use the following construct in NameStudents():
struct Hjava_lang_String* hStudentName = (struct Hjava_lang_String *)
(unhand(JavalStudents)->body)[i];
lStudents[i] = makeCString(hStudentName);
This method accesses the body
variable by first unhanding the JavalStudents
(struct HArrayOfString*) variable using unhand().
As explained previously, body
is a pointer to a list of String
handles; to obtain the ith
name handle in the list, you simply suffix an array index [i]
to the body pointer. By iterating
with a loop over the entire class list, you can fill lStudents-the
C (char *) version of the
class list-with the names of the students in the class. lStudents
is then used by the GetStudentAvg()
native method to search for the student's grade records by name.
Accessing a Float Array
Accessing an array of an intrinsic Java type is easier than accessing
an array of Java classes. In the GradeBook
class, the AddTest() method
accepts a list of float test
scores for each student. Its native method implementation prototype
looks like this:
long GradeBook_AddTest(struct HGradeBook *hthis,
struct HArrayOfFloat *lTestScoresIN);
Again, you have a handle to an array of objects, but this time,
the handle is to ArrayOfFloat.
Accessing ArrayOfFloat is
very similar to accessing the String
array, but because float
is an intrinsic type, you can simply cast the body
pointer into a (float *).
This pointer can then be used as a normal (float
*) to copy the elements of the array from the Java
array object into your C array object:
float* lJavaTestScores = (float *)(unhand(lTestScoresIN)->body);
float* lCTestScores = new float[nStudents];
int i;
for (i=0; i < nStudents; i++) {
lCTestScores[i] = lJavaTestScores[i];
}
lTests[nTests] = lCTestScores;
nTests++;
A sample Java application, TeachersPet,
uses the GradeBook class
and is shown in Listing 33.13. This application creates a new
GradeBook with five students
and three sets of test results. With this database created, the
application then finds the overall average for each student and
the average for the class as a whole.
Listing 33.13. TeachersPet.java.
public class TeachersPet {
public static void main(String argv[]) {
int nStudents = 5;
int nTests = 3;
GradeBook myClass = new GradeBook(nStudents, nTests);
String lszStudents[] = new String[nStudents];
lszStudents[0] = new String("Susan Harris");
lszStudents[1] = new String("Thomas Thompson");
lszStudents[2] = new String("Blake Cronin");
lszStudents[3] = new String("Rotten Johnson");
lszStudents[4] = new String("Harrison Jackson");
myClass.NameStudents(lszStudents);
float lTest1Grades[] = new float[nStudents];
lTest1Grades[0] = 93;
lTest1Grades[1] = 86;
lTest1Grades[2] = 89;
lTest1Grades[3] = 65;
lTest1Grades[4] = 78;
myClass.AddTest(lTest1Grades);
float lTest2Grades[] = new float[nStudents];
lTest2Grades[0] = 100;
lTest2Grades[1] = 83;
lTest2Grades[2] = 91;
lTest2Grades[3] = 55;
lTest2Grades[4] = 83;
myClass.AddTest(lTest2Grades);
float lTest3Grades[] = new float[nStudents];
lTest3Grades[0] = 89;
lTest3Grades[1] = 94;
lTest3Grades[2] = 82;
lTest3Grades[3] = 59;
lTest3Grades[4] = 85;
myClass.AddTest(lTest3Grades);
for (int cStudent = 0, float fStudentAvg=0.0f; cStudent < nStudents;
cStudent++) {
fStudentAvg = myClass.GetStudentAvg(lszStudents[cStudent]);
System.out.println(lszStudents[cStudent]+"'s average on the 3 tests is
"+fStudentAvg);
}
for (int cTest = 1, float fClassAvg=0.0f, float fTestAvg = 0.0f;
cTest < nTests+1; cTest++) {
fTestAvg = myClass.GetTestAvg(cTest);
System.out.println("The class average on Test #"+cTest+" is "+fTestAvg);
fClassAvg += fTestAvg;
}
fClassAvg /= ((float)nTests);
System.out.println("\nThe class average on the 3 tests is "+fClassAvg);
}
}
Compile the GradeBook class
library and the sample application TeachersPet,
just as you did with the preceding two examples, and give the
application a test run:
C:\java\classes\SortedList> java TeachersPet
Susan Harris's average on the 3 tests is 94
Thomas Thompson's average on the 3 tests is 87.6667
Blake Cronin's average on the 3 tests is 87.3333
Rotten Johnson's average on the 3 tests is 59.6667
Harrison Jackson's average on the 3 tests is 82
The class average on Test #1 is 82.2
The class average on Test #2 is 82.4
The class average on Test #3 is 81.8
The class average on the 3 tests is 82.1333
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 a methodology
for building an interface to existing C libraries using a very
simple signal-processing library as an example.
Next, you investigate interfacing to C++ libraries by developing
a wrapper system for overcoming Java's C-only native method interface
using a very simple 3D library as an example. As you go along,
you examine all the problems in interfacing to this library.
A tremendous number of C libraries exist in the development world
today-primarily because of the popularity of the C language. Because
of either the time necessary to develop these legacy libraries,
or the sheer performance required of them, it may be very difficult
to port a C library entirely to Java. Instead, it may be necessary
to develop a Java interface to the library.
Interfacing C with Java is difficult for one main 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 Java's object
requirement. 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 have some difficulty because
the interface won't be object oriented. As a solution to this
dilemma, consider the feasibility of developing an object-oriented
interface to your library. In the next section, we look at how
this can be done.
Building an Object-Oriented Wrapper Around the C Library
Listing 33.14 shows 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 sin FT. Notice how it isn't just a static wrapper for the
FFT functions, but rather a class library that encapsulates data-sampling
functions with a set of FFT functions added.
Listing 33.14. The DataSample
class.
package mySigProcLib;
class DataSample {
public void AddSample(float sample) {
// ...Some signal management logic here...
}
public void DeleteSample(int I) {
// ...Some more 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 33.15. There's nothing new 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 33.15. The 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 of interest in object-oriented design has come
an explosion in the number of available C++ class libraries. This
section extends the discussion of interfacing to cover the development
of an interface to existing C++ libraries. To make the description
of this design process more concrete, we'll take an existing C++
class library and develop a parallel set of Java classes that
transparently classes provide the same look and feel as the C++
classes.
The My3D
C++ Graphics Class Library
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.
Assume that the My3D C++
library is either too performance-sensitive to be converted to
Java, or that redevelopment of the Java library would involve
so much time that developing a Java interface to the C++ class
is a better investment.
The My3D World
Object
Listing 33.16 contains the World
object, the first C++ object we'll consider in this simple 3D
library. The World object
is responsible for handling the details of attaching and detaching
objects from a 3D scene. The methods AttachNode()
and DetachNode(), as you
may have guessed, are responsible for taking a Node
object and attaching or detaching the Node
from the scene graph.
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 public
methods and variables for the class.
Listing 33.16. World
C++ class definition.
class World {
public:
void AttachNode (Node* theNode);
Node* DetachNode (Node* RemoveNode);
private:
Node** NodeList;
};
The My3D Node
Object
The next class in the My3D
C++ class library is the Node
class, shown in Listing 33.17. This class contains the mechanisms
necessary to give an object in 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 Listing 33.18).
Listing 33.17. Node
C++ class definition.
class Node {
public:
void SetLocation (PointFW_t& loc);
PointFW_t GetLocation();
private:
PointFW_t theLocation;
}
Listing 33.18. PointFW_t
C++ class definition.
typedef struct PointFW_t {
float x;
float y;
float z;
float w;
} PointFW_t;
The My3D Light
Object
The Light class is a subclass
of Node, which makes it attachable
within your World scenes
(see Listing 33.19) and gives it 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 33.19. 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;
};
The My3D PointF_t
and ColorF_t Objects
The Light class also introduces
two new data structures, PointF_t
and ColorF_t (see List-
ings 33.20 and 33.21). 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 33.20. PointF_t
C++ struct
definition.
typedef struct PointF_t {
float x;
float y;
float z;
} PointF_t;
Listing 33.21. ColorF_t
C++ struct
definition.
typedef struct ColorF_t {
float r;
float g;
float b;
} ColorF_t;
The My3D Geometry
Object
The final class in the 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.
Two methods 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 33.22.
Listing 33.22. 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;
}
The InterfaceObject
Base Class
Now you can move on to the real task of building Java objects
that interface with the C++ classes you have just developed. Before
doing this, however, you first need a class that encapsulates
all the interface information you need about your C++ class, the
InterfaceObject class (see
Listing 33.23).
Listing 33.23. Java InterfaceObject
definition.
package My3D;
public class InterfaceObject {
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
is 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.
The My3D
World
Java Object Definition
Let's start interfacing your library to Java with the World
class; the implementation of the Java class is shown in Listing
33.24.
Listing 33.24. 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 implementations before this illusion
can become a reality for your Java class library users.
The My3D
World
Constructor Interface
Let's start at the beginning, with the Java World
class constructor. As you saw in Listing 33.24, the World
Java implementation class calls a native constructor in its constructor.
You want your native constructor to accomplish the following tasks:
- Instantiate the parallel C++ object-in this case, a C++ World
object.
- Store the pointer to this instantiated class and store an
integer reference to the array position of this pointer in the
Java interface object (which is referred to as the handle
in the rest of this section).
- Initialize the reference counter for this pointer to 1,
to indicate that only one Java object is using it.
- Remember what kind of object it is by saving the class type
as a constant in the Java object.
The native constructor implementation for the World
class is shown in Listing 33.25.
Listing 33.25. World
interface constructor.
long My3D_World_AllocLength = 0;
#define INITIAL_My3D_World_ALLOC 2
World** My3D_World_ObjPtr = NULL;
long* My3D_World_ObjRef = NULL;
void My3D_World_constructor(struct HMy3D_World *jthis) {
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.
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;
}
The My3D_World_Resize()
Function
In every instantiable class considered in this chapter, your interface
functions maintain a list of pointers to all the C++ objects you
have instantiated indirectly by instantiating its Java interface
class. In the preceding constructor, the first statement checks
to see whether this list should be initialized:
if (My3D_World_AllocLength == 0) {
My3D_World_AllocLength =
My3D_World_Resize(INITIAL_My3D_World_ALLOC);
}
My3D_World_Resize() is a
helper function 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 considered in this chapter. The code for the My3D_World_Resize()
function is shown in Listing 33.26.
Listing 33.26. My3D_World_Resize()
function definition.
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 the constructor 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 (for example,
fewer than eight).
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 any problems in a graceful
manner.
The Reference Counter Functions
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 the Java
objects. Because multiple Java interface objects can refer to
a single C++ pointer, you must 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() and DecRefCntr()
functions is shown in Listing 33.27.
Listing 33.27. IncRefCntr
function definition.
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 33.28.
Listing 33.28. DecRefCntr()
function definition.
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 you
set out to do in your World
constructor.
Passing a Call from Java to C++
Now 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 different
C++ format) to the C++ version of the class.
Listing 33.29 shows the implementation for the AttachNode()method:
Listing 33.29. AttachNode()
implementation.
void My3D_World_AttachNode (struct HMy3D_World *hthis, struct HMy3D_Node *hNode) {
My3D_World_ResolvePtr(hthis)->AttachNode(My3D_Node_ResolvePtr(hNode));
}
The AttachNode() function
prototype should look very familiar to you from earlier in this
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 parameter. 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: The call to My3D_World_ResolvePtr()
looks up the World C++ pointer
referenced by this and calls this World
object's AttachNode() method
with the Node object referenced
by the hNode and resolved
by using My3D_Node_ResolvePtr().
Using My3D_xxxx_ResolvePtr()
The My3D_World_ResolvePtr()
and My3D_Node_ResolvePtr()
functions 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 is shown in Listing 33.30.
Listing 33.30. ResolvePtr()
function definition.
World* My3D_World_ResolvePtr(struct HMy3D_World *jthis) {
return( My3D_World_ObjPtr[unhand(jthis)->hCPPObj] );
}
ResolvePtr() 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 is
the final explicit method needed for the World
object. Its implementation is shown in Listing 33.31.
Listing 33.31. DetachNode()
function definition.
struct HMy3D_Node* My3D_World_DetachNode(struct HMy3D_World *hthis,
struct HMy3D_Node *hNode) {
Node* CPPNode = My3D_World_ResolvePtr(hthis)->DetachNode (Node_ResolvePtr(hNode));
ClassClass *ccNode = NULL;
ccNode = FindClass(NULL, "My3D/Node\0", TRUE);
assert(ccNode != NULL);
struct HMy3D_Node *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. Earlier in this 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 have 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 encapsulates this handle to return.
Before you instantiate a Java Node
class, you must fill out a ClassClass
structure (as discussed earlier in this section). 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 want to call. This signature indicates the types
you want to pass and receive from the invoked Java method. The
signature passed in the preceding code ("()")
is the simplest-a constructor that accepts no parameters and returns
nothing. All constructors do not have a return type, but if you
have a constructor in the Node
class that accepts 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 statement
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:
- Find the Java Node handle
that corresponds to the C++ Node
pointer returned by DetachNode()
and set hNode's hCPPObj
to it.
- Increment the reference counters to the C++ pointer and decrement
the reference counter for the current hNode
pointer.
- Copy the handle into the hNode
object so that it points at the correct object.
All 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 33.32. It emulates a pointer reference system
for your interface code.
Listing 33.32. Node PtrEmul()
function definition.
void My3D_Node_PtrEmul(Node* pNode, struct HMy3D_Node* hNode) {
long 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. The function then decrements the
reference count for the target's current Node
C++ object because it is no longer 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.
The World
Class's FindHandle()
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 object's
My3D_World_ObjPtr array.
The implementation for this is shown in Listing 33.33.
Listing 33.33. World FindHandle()
implementation.
long My3D_World_FindHandle(World *FindWorld) {
for (long 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 that there is 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 is anticipated,
this function should be rewritten with a hashing search method
to reduce its performance impact.
The World
Class's Finalize()
Method
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 has 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 33.34.
Listing 33.34. World finalize()
implementation definition.
void My3D_World_finalize(struct HMy3D_World *jthis) {
My3D_World_DecRefCntr(unhand(jthis)->hCPPObj);
}
The Node
Java Interface Object
The next interface implementation is the interface to the Node
object. Listing 33.35 shows the Java object definition. It doesn't
look much different from its C++ counterpart (which is helpful
for any converts you may have from your C++ product).
Listing 33.35. 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 have 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 similar to the C++ version, and its implementation
is shown in Listing 33.36.
Listing 33.36. 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 33.37.
Listing 33.37. SetLocation()
implementation.
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.z = unhand(hLocation)->z;
My3D_Node_ResolvePtr(hthis)->SetLocation(CPPLocation);
}
This implementation is very similar to the AttachNode()
interface function from earlier in this 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 de-referencing the pointer because of
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 is SetLocation (<x,y,z,w>). Although the correlation is one-to-one
with Visual C++ 4.0 and JDK 1.0, it isn't set in stone. The safe programming practice is to
assume no correlation and copy the elements one by one. In any case, compared to the overhead
of calling the native method, the performance impact of these four copies is negligible.
|
The native implementation of the GetLocation()
method is shown in Listing 33.38.
Listing 33.38. GetLocation()
implementation.
struct HMy3D_PointFW_t* My3D_Node_GetLocation() {
PointFW_t CPPLocation = My3D_Node_ResolvePtr(hthis)->GetLocation();
ClassClass *ccNode = FindClass(NULL, "My3D/PointFW_t\0", TRUE);
assert(ccNode != NULL);
struct HMy3D_PointFW_t *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 similar 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.
The Light
Java Interface Object
Listing 33.39 shows 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 strive to keep your object hierarchies the same between
your Java and C++ versions to prevent difficulties in interfacing
the two libraries and to 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 33.40.
Listing 33.39. 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 33.40. 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 33.41.
Listing 33.41. SetDirection
and GetDirection
implementation.
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.z = unhand(hDirection)->z;
My3D_Light_ResolvePtr(hthis)->SetDirection(CPPDirection);
}
struct HMy3D_PointF_t* My3D_Light_GetDirection() {
PointF_t CPPDirection = My3D_Light_ResolvePtr(hthis)->GetDirection();
ClassClass *ccLight = FindClass(NULL, "My3D/PointF_t\0", TRUE);
assert(ccLight != NULL);
struct HMy3D_PointF_t *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 similar to the implementation of the SetLocation()
and GetLocation() methods
in the Node class.
The Geometry
Java Interface Object
The final interface class you need to know is the Geometry
class. The Java interface class for the Geometry
object is shown in Listing 33.42 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 33.42. 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 33.43 shows the implementation
of the ColorFA_t class.
Listing 33.43. 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 33.44. 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 33.44. Geometry
constructor implementation.
long My3D_Geometry_AllocLength = 0;
#define INITIAL_My3D_Geometry_ALLOC 2
Geometry** My3D_Geometry_ObjPtr = NULL;
long* My3D_Geometry_ObjRef = NULL;
void My3D_Geometry_constructorb(
struct HMy3D_Geometry *jthis,
short cNP, short cNV, HArrayOfObject* pAP,
HArrayOfObject* pAN, HArrayOfObject* pFN,
HArrayOfObject* pAT, HArrayOfObject* pAC,
if (My3D_Geometry_AllocLength == 0) {
My3D_Geometry_AllocLength =
My3D_Geometry_Resize(INITIAL_My3D_Geometry_ALLOC);
}
long pos;
for ( pos=0;
(pos != My3D_Geometry_AllocLength) &&
(My3D_Geometry_ObjPtr[pos] != NULL);
pos++ )
;
if (pos == My3D_Geometry_AllocLength) {
My3D_Geometry_AllocLength =
My3D_Geometry_Resize(My3D_Geometry_AllocLength*2);
}
int PassNP = cNP;
int 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);
assert (My3D_Geometry_ObjPtr[pos] != NULL);
My3D_Geometry_IncRefCntr(pos);
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 handle HArrayOfObject.
When unhand() is applied
to the handle HArrayOfObject,
unhand() returns 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 33.45 shows the implementation of the My3D_Node_ResolvePtr()
function.
Listing 33.45. 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 as when Node was used
in your C++ library), special mechanisms must be built in your
superclass interface implementation to handle this requirement.
For example, when you attach a Light
object to 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. 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 to 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 have 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 33.46),
which also contains all the public interface functions for each
of the objects for linking.
Listing 33.46. 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*, you
must modify the C++ subclass to tell you what class it represents.
To do this in your graphics library, add to the C++ class a method
int KindOf() that returns
the object type as defined in the global header file in Listing
33.46. With this information, the class can chain the request
of My3D_xxxx_FindHandle()
down to the correct class interface code, as shown in Listing
33.47.
Listing 33.47. Node
C++ FindHandle()
implementation.
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 33.48.
Listing 33.48. Node
C++ DecRefCntr()
implementation.
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;
};
}
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 final component you should 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. The behavior you want in your interface code is to
adjust the reference counters appropriately. You accomplish this
simply by linking the call to My3D_xxxx_finalize()
with a call to My3D_xxxx_DecRefCntr(),
as shown in Listing 33.49 for the World
class. All Java classes that link to C++ classes that can be instantiated
should include interface code for finalize()
methods like this (that is, in the classes you have seen in this
chapter, the Node class should
be the only class that doesn't include code like this).
Listing 33.49. IncRefCntr()
implementation.
void My3D_World_finalize(struct HMy3D_World* hthis) {
My3D_World_DecRefCntr(unhand(hthis)->hCPPObj);
}
A Sample Program
That 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 33.50. Because this simple
3D library doesn't have enough functionality to render the scene,
you can't run the program; but note how the semantics of the Java
version of the My3D application
are the same as the semantics of a normal Java program, meeting
the design goals at the beginning of the chapter.
Listing 33.50. 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);
}
}
This chapter covered a lot of ground! Take some time to let it
soak in before you continue. The first section of the chapter
explained the basics of the Java native method interface and how
Java passes data to your C/C++ program and how C/C++ programs
should pass data back to Java.
The second half of the chapter dealt with the issues of building
a set of Java interface libraries to legacy C and C++ libraries.
Because of limitations on the scope of this chapter, we didn't
get a chance to go into how to handle all the limitations of Java's
lack of some object-orientation features (such as multiple inheritance
issues and templates). If you're interested in how to handle these
types of problems, take a look at Chapter 31
in Tricks of the Java Programming Gurus, published by Sams.net.
Contact
reference@developer.com with questions or comments.
Copyright 1998
EarthWeb Inc., All rights reserved.
PLEASE READ THE ACCEPTABLE USAGE STATEMENT.
Copyright 1998 Macmillan Computer Publishing. All rights reserved.