Implementing Native Methods |
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
TheInputFile_close()
andOutputFile_close()
function both accept one argument: the automatic parameter passed into the function by the Java runtime system.Notice first the declaration for the automatic parameter in the// in InputFileImpl.c void InputFile_close(struct HInputFile *this) . . . // in OutputFileImpl.c void OutputFile_close(struct HOutputFile *this) . . .InputFile_close()
function signature. The InputFile object, that is, the object whoseInputFile_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'sread()
method and OutputFile'swrite()
method both take an integer argument:The first statement declares that the// in InputFile.java public native int read(byte[] b, int len); // in OutputFile.java public native int write(byte[] b, int len);read()
method's second argument is an integer. The second statement declares thatwrite()
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:Ignore the middle argument for now--it's covered later.// in InputFileImpl.c long InputFile_read(struct HInputFile *this, HArrayOfByte *b, long len)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, theOutputFile_write()
function accepts along
where a Javaint
was passed in: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// in OutputFileImpl.c long OutputFile_write(struct HOutputFile *this, HArrayOfByte *b, long len)InputFile_read()
andOutputFile_write()
uses thelen
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 Cstruct
. 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'swrite()
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:
The first statement declares that the first argument to the// in InputFile.java public native int read(byte[] b, int len); // in OutputFile.java public native int write(byte[] b, int len);read()
method is an array of bytes. The second statement declares that the first argument to thewrite()
method is also an array of bytes.On the C side, the declarations for the two functions that implement the
read()
andwrite()
methods look like this: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// 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)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 isArrayOf
plus the data type of the elements contained within the array. So, the fully qualified class name of an array of bytes isArrayOfByte
, and the fully qualified class name of an array of integers isArrayOfInt
.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!
Implementing Native Methods |