Previous | Next | Trail Map | Integrating Native Methods into Java Programs | Implementing Native Methods


Passing Data into a Native Method

As you saw on the previous page, the Java runtime system passes an extra argument to native methods as the first argument in the argument list. Let's investigate this a little further, and then look into how you can pass your own arguments to a native method.

The Automatic Parameter

The InputFile_close() and OutputFile_close() function both accept one argument: the automatic parameter passed into the function by the Java runtime system.
    // in InputFileImpl.c 
void InputFile_close(struct HInputFile *this)
    . . .
    // in OutputFileImpl.c 
void OutputFile_close(struct HOutputFile *this)
    . . .
Notice first the declaration for the automatic parameter in the InputFile_close() function signature. The InputFile object, that is, the object whose InputFile_close() is being called, is passed into the native method through a handle to a structure. The name of the structure is comprised of the capital letter 'H' (presumably for "Handle") and the name of the Java class.

You can use this handle to reference the object's member variables from within the native method. Using a Java Object in a Native Method covers this in detail.

When you write a native method that accepts some object as an argument, you will notice that those objects are also passed into the native method as a handle to a structure. It's the same mechanism used by the runtime system to pass in the automatic parameter. More about this later on this page.

Passing Primitive Data Types

You can pass primitive data types--integers, boolean, floats, and so on--into a native method. InputFile's read() method and OutputFile's write() method both take an integer argument:
    // in InputFile.java 
public native int read(byte[] b, int len);

    // in OutputFile.java 
public native int write(byte[] b, int len);
The first statement declares that the read() method's second argument is an integer. The second statement declares that write() method's second argument is also an integer. You can ignore the first argument for now, it's covered later on this page.

Besides integers, you can also pass floats, booleans, doubles, characters, and other primitive data types to a native method. These primitive data types are mapped to the closest matching native language data type. For example, on the C side of our example, the function signature for the InputFile_read() function looks like this:

    // in InputFileImpl.c 
long InputFile_read(struct HInputFile *this,
                    HArrayOfByte *b,
                    long len)
Ignore the middle argument for now--it's covered later.

Notice that the function accepts the integer argument as a long. That's because C long integers are the nearest match to Java integers. Similarly, the OutputFile_write() function accepts a long where a Java int was passed in:

    // in OutputFileImpl.c 
long OutputFile_write(struct HOutputFile *this,
                      HArrayOfByte *b,
                      long len)
Now that you've got the primitive data passed into the method, you'd probably like to use it. Well, you can use these primitive data type arguments just as you would any other C variable: by name. For example, this code snippet that occurs in both InputFile_read() and OutputFile_write() uses the len argument to set the number of bytes to be read or written.
if (count < len) {
    actual = count;
} else {
    actual = len;
}

Note that primitive data type arguments are passed to the native function by value: that is, if you modify one of the arguments within the native function, the change will not affect the calling Java method. To return a value or set of values through a native method's arguments, use an object.

Passing Reference Data Types

In addition to primitive types, you can pass reference data types into native methods as well. Reference data types include arrays, strings, and objects all of which are first-class Java objects. Java objects are passed into a native method as a handle to a C struct. Indeed, the Java runtime system uses this mechanism to pass in the Java object whose native method is being called. You saw this in action in the The Automatic Parameter section earlier on this page.

InputFile's read() method accepts a byte array--the method reads bytes from the input file and returns them through this array. OutputFile's write() method also accepts a byte array--the method writes the bytes contained in this array to the output file.

The Java side of a native method that takes an object argument is straightforward: simply declare the method as though it were a "regular" Java method:

    // in InputFile.java
public native int read(byte[] b, int len);

    // in OutputFile.java
public native int write(byte[] b, int len);
The first statement declares that the first argument to the read() method is an array of bytes. The second statement declares that the first argument to the write() method is also an array of bytes.

On the C side, the declarations for the two functions that implement the read() and write() methods look like this:

    // in InputFileImpl.c
long InputFile_read(struct HInputFile *this,
                    HArrayOfByte *b,
                    long len)

    // in OutputFileImpl.c
long OutputFile_write(struct HOutputFile *this,
                      HArrayOfByte *b,
                      long len)
The second argument to both functions is the byte array argument passed in from the Java side. As you know Java arrays are first class objects. Thus, the array, like an object, is passed in as a handle to a C struct. The structure name is comprised of the capital letter 'H' and the name of the class. Notice, however, that the fully qualified name of the class is used. In the case of arrays, the fully qualified name of the class is ArrayOf plus the data type of the elements contained within the array. So, the fully qualified class name of an array of bytes is ArrayOfByte, and the fully qualified class name of an array of integers is ArrayOfInt.

The fully qualified name of classes include the name of the package that the class is declared in. Thus a String is passed in as a handle to a Hjava_lang_String structure. An Object is passed in as a handle to a Hjava_lang_Object structure, a Stack is passed in as a handle to a Hjava_util_Stack structure, and so on.

Now that you've got a handle in the C function to a Java object, what can you do with it? You can use it to access the object's member variables. This is covered in Using a Java Object in a Native Method.

A Word of Warning

Due to the background activities of the garbage collector, you must be careful to maintain references to all objects passed into a native method to prevent the object from being moved to another location on the heap. The reference must be on the C stack. It can't be a global variable. Typically, you don't need to worry about this because the argument passed into the native method is normally sufficient to keep the garbage collector informed of a reference to a particular object. However, if you are working with global variables or doing unorthodox pointer manipulations, keep in mind that the garbage collector needs to be kept apprised of any references in a native method to Java objects.

Note that the garbage collector does not recognize a pointer to the middle of an object as a reference to that object. So references that you maintain to an object must point the beginning of the object!


Previous | Next | Trail Map | Integrating Native Methods into Java Programs | Implementing Native Methods