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 [PENDING: is it true you can get class vars too?] 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 buffer, passed into InputFile_read() function you use the unhand() macro like this:
unhand(buffer)
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 must be included in the C source file where the native method is implemented. This file is included into both InputFileImpl.c and OutputFileImpl.c through the inclusion of StubPreamble.h.

Accessing the Object's Member Variables

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

buffer, 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 [PENDING: whatzits]. The InputFile_read() function gets a pointer to the body element of buffer with this code:

char *data = unhand(buffer)->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 name as the corresponding variable 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. This table shows the correspondence between data types in Java and data types in C.

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 [PENDING: where] 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. So, 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. Let's focus now on the method, doubleUp(), in the NativeExample class. The implementation of 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 the Java environment [PENDING: environment of what?]. Most of the time, you will use "0" for the value of this argument to tell the Java runtime system to use the current Java environment.

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 [PENDING: WHAT IS THIS?]

The fourth argument is the signature of the constructor that you want to call. This argument indicates whether there are any remaining arguments that need to be passed into execute_java_constructor() and how many. [PENDING: more here about this.]

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 0 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 Java environment. Again, most of the time, you will use "0" for the value of this argument to tell the Java runtime system to use the current Java environment.

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 instead of an object.


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