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


Using a Java Object in a Native Method

Now that you've got a handle to a Java object in your native method, what can you do with it? You can use it to access the object's member variables. But first, you have to dereference the handle.

Dereferencing the Handle

You apply the unhand() macro to the object handle to dereference it. When you dereference a handle you get a pointer to a C struct that parallels the Java object's structure. For example, to dereference the byte array, named b, passed into InputFile_read() function you use the unhand() macro like this:
unhand(b)
unhand() returns a pointer to a C structure. You can use normal C syntax to access the elements in the structure. The elements in the structure parallel the member variables of the objects. Thus, you can access the member variables of an object through the pointer returned from unhand().


Note: The unhand() macro is defined in the header file interpreter.h, which is included in StubPreamble.h.

Accessing the Object's Member Variables

You can use the pointer returned from unhand() like any other C struct pointer, accessing its members with the -> operator.

The b argument in the InputFile_read() function is a byte array. The structure in C that maps to Java arrays contains an element named body which is a C array of char. The InputFile_read() function gets a pointer to the body element of b with this code:

char *data = unhand(b)->body;
Now that the InputFile_read() function has a C pointer to the body of the array, it can gleefully read bytes into it, which it does with this line of code:
actual = read(unhand(this)->fd, data, actual);
The struct members have the same names as the corresponding variables in the Java class. So you can use this same mechanism to access an InputFile object's member variables. Indeed, the InputFile_open() function uses this mechanism to access an InputFile object's path and fd variables:
long InputFile_open(struct HInputFile *this)
{
    int         fd;
    char        buf[MAXPATHLEN];

    javaString2CString(unhand(this)->path, buf, sizeof(buf));
    convertPath(buf);

    fd = open(buf, O_RDONLY);
    if (fd < 0)
        return(FALSE);

    unhand(this)->fd = fd;
    return(TRUE);
}
Note that the data type of a variable in the C struct is the nearest matching C data type to the Java type of the member variable in the object. Be sure to declare and use the structure elements with the correct data types.

Calling Java Methods from a Native Method

You can call a Java object's methods from a native method, but you use a different mechanism for that than you use for accessing its member variables. For example, this
ptr->methodname();   // doesn't work
does not work. Rather you use one of the utility functions declared in interpreter.h for this purpose.
execute_java_dynamic_method()
calls an instance method from a native method
execute_java_static_method()
calls a class method from a native method
execute_java_constructor()
creates a new Java object from within a native method

The character-replacement program doesn't use any of these methods. Let's look at another example that does. The new example program is a collection of methods that illustrate how you can perform various tasks from within a native method. The example program, NativeExample, was generously provided to us by Thomas Ball of the Java team.

Let's focus now on the doubleUp()method in the NativeExample class. The implementation of the doubleUp() method uses execute_java_constructor() to create an object from within a native method, and then execute_java_dynamic_method() to call one of the object's instance methods. This method returns the object it created.

On the Java side, doubleUp() is declared like this:

native NativeExample doubleUp();
On the C side, doubleUp() is implemented like this:
struct HNativeExample *
NativeExample_doubleUp(struct HNativeExample *hInst)
{
    HNativeExample *hNewInst;
    long twoX;

    hNewInst = (HNativeExample *)execute_java_constructor(
        0, "NativeExample", 0, "(I)", unhand(unhand(hInst)->myValue)->value);

    twoX = (long)execute_java_dynamic_method(
        0, (HObject *)hNewInst, "twoTimes", "()I");

    unhand(unhand(hNewInst)->myValue)->value = twoX;
    return hNewInst;
}
The interesting bits of the doubleUp() function are in bold.

Calling a Java Constructor

Let's look at the first bold line in the code above--the call to execute_java_constructor(). The execute_java_constructor() function requires four arguments, but may have more.

The first argument to execute_java_constructor() is represents the execution context. For now, you should use use 0 for the value of this argument to tell the Java runtime system to use the current execution context. In future releases of Java this argument won't be necessary and will likely be removed.

The second argument is the name of the class you want to instantiate. The example creates a new NativeExample object, so the value of the second argument in that example is the string "NativeExample".

The third argument is the class structure for the class you are instantiating. This argument is redundant because it provides the same information as the second argument--the class to instantiate. However, the execute_java_constructor() function must do a class look up if you provide only the class name in the second argument and use 0 for the third argument (as shown in the example). This can cause performance degradation if you are constructing a lot of objects from the same class. If performance is important to you, you can avoid unnecessary class lookups by providing a value for this argument with the FindClass() function defined in interpreter.h.

You use the fourth argument to specify the Java constructor that you want to invoke. The name of the Java constructor is the name of the class you are instantiating--NativeExample() in the example. However, because of method name overloading, you need to be more specific and indicate exactly which constructor you want by specifying the number and type of its arguments. You will also use signature arguments when calling execute_java_dynamic_method() and execute_java_static_method().

The example uses the following signature argument, which specifies the NativeExample constructor that takes one integer argument:

(I)

The general form of a signature argument is:

(arguments)returnvalue
arguments is a series of single keyletters that indicate the data type for an argument. The number of keyletters indicates the number of arguments. returnvalue is a keyletter that indicates the data type of the return value. The keyletters are defined in signature.h. Look in that file to find the keyletters that identify other data types such as float, char and so on. Note that a signature argument used with execute_java_constructor() cannot indicate a return value as Java constructors don't return a value.

The general form of execute_java_constructor() is:

HObject *execute_java_constructor(ExecEnv *env,
                                 char *classname,
                                 ClassClass *cb,
                                 char *signature, ...);
The total number of arguments to the constructor is determined by the signature argument. The signature also indicates which of the classname constructors is called. This function returns the new object, or null if there is an error.

Calling an Instance Method

After the doubleUp() function creates a new NativeExample object with execute_java_constructor(), it calls the object's twoTimes() instance method:
twoX = (long)execute_java_dynamic_method(
    0, (HObject *)hNewInst, "twoTimes", "()I");
The execute_java_dynamic_method() function requires four arguments, but may have more depending on which method you are calling.

The first argument to execute_java_dynamic_method() is the same as the first argument to execute_java_constructor()--it's the execution context. Again, you should use 0 for the value of this argument to tell the Java runtime system to use the current execution context. In future releases of Java this argument won't be necessary and will likely be removed.

The second argument is the object whose instance method you want to execute. The third argument is the name of the instance method to execute. And finally, the fourth argument is the signature of the instance method. As with execute_java_constructor() the signature argument indicates if there are any remaining arguments, how many, and what type. You formulate the signature argument for execute_java_dynamic_method() as you did previously with execute_java_constructor().

The general form of execute_java_dynamic_method() is:

long execute_java_dynamic_method(ExecEnv *env, HObject *obj,
                                char *method_name, char *signature, ...);

Calling a Class Method

To call a class method from a native method use:
long execute_java_static_method(ExecEnv *env, ClassClass *cb,
                               char *method_name, char *signature, ...);
This function is analogous to execute_java_dynamic_method above, except that you provide a class structure instead of an object. You can get the class structure using the FindClass() function defined in interpreter.h.


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