Friday 15 May 2015

Using Java Objects from Native Methods

 In native methods, all objects appear as handles. Handles are at the heart of the Java object model, but you will be shielded from directly manipulating handles. A group of C macros and functions permit you to deal with handles largely as an abstract type. So what can you do with a handle?

Accessing Instance Variables from Native Methods

For starters, you can access all of an object's instance variables-all those that are visible to your code, at least. In order for your native method to access the data members of your object, you dereference the handle with the macro unhand(). Unhand() is defined in the header file interpreter.h, which is included by StubPreamble.h. Listing 19.8 shows an example of accessing an object's instance variables from a native method.

Listing 19.8. Using unhand()to access instance variables.

typedef struct ClassUserInformation { 
   long iUserID;
} ClassUserInformation;
HandleTo(UserInformation);
...
void UserInformation_SetUserID(struct HUserInformation *hUI,
    long newValue)
{
   unhand(hUI)->iUserID = newValue;
}


What Are Handles, Anyway?
In the Java implementation, a handle is used to maintain references to objects. Handles allow the use of native languages to truly be "two-way." That is, the native methods can access instance variables and call other methods, just as pure Java methods can. Handles are at the very heart of the Java object model. In code, a handle is a small structure with two pointers, which together form an object instance. One points to the instance variables, the other to the method table (the vtable in C++ parlance). Handles for your classes are created automatically when you create the header file. (See the sidebar "Where Did HUserInformation Come From?")
By using pointers to handles to pass objects around, Java attains great efficiency. Because object manipulations work through handles, there is a single item of interest to the garbage collector, and a natural mechanism for marking objects in use or discarded.
The unhand() macro returns a pointer to an ordinary C structure. (For handles to objects of your class, it returns a pointer to the structure defined in your header file.) In all cases, the members of the C structure have the same names as the members of the Java class. The types may be different, however. Check Table 19.1 for the type mapping. You can read and write to the members of the structure. In fact, you can even pass pointers to them as arguments to other functions. They behave in all ways as members of ordinary C structures, because that is exactly what they are. When you use unhand()on a handle, you are actually looking at the fundamental Java implementation of an object. The structures you see using the handle are the real structures; there is no behind-the-scenes marshalling before the call.

Accessing Class Variables from Native Methods

Class variables (static variables) do not appear in the instance variable structure generated by javah. This is because they are not stored with each instance of a class, but rather with the class structure itself. The Java runtime provides a function that you can use to get a pointer to the class variable getclassvariable.
long *getclassvariable(struct ClassClass *cb, char *fname);
Notice that this function, like many other Java runtime functions, requires a pointer to the class structure. Fortunately, every object instance carries this pointer around. The obj_classblockmacro from interpreter.htakes an object handle as an argument and returns a pointer to the class structure.
Why does getclassvariablereturn a pointer to a long? It returns a pointer because the native language code should be able to modify the class variables, as well as see them. The return type is long, but in C, all pointers to data are the same size. So, it is safe to cast the returned pointer to a pointer to whatever type the class variable really is.
Be warned that unless your native methods are synchronized, modifying class variables can be problematic. Always remember that, even though this method is in C, it really is being called from a multithreaded environment. There may be other threads executing Java methods or native methods. They may be doing any number of things, including accessing the class variable at exactly the same time as the native method. Any time global data is involved, whether it is class-wide or truly global, synchronization is an issue. See "Multithreading and Native Methods" for more details.
Wouldn't it be nice if your native language code could also call Java code? Yes, it really would. Fortunately, we can do exactly that. It all starts with the object handle. (Where else?) You have to use a slightly more complicated mechanism than you might think at first. Calling another C function from a function pointer in a structure is something familiar to all of us, something like:
hUserInformation->Username();                 // DO NOT DO THIS!
Although this would have the net effect of transferring execution to the other function, it is not sufficient. Here's why: When calling a C function, a stack frame is created with the return address and some arguments. (Implementations vary somewhat; this is necessarily a very general description.) In Java, there is much more context provided for a function call. In addition to the processor stack, the JVM maintains a great deal of stack information on its own, internal stack. (Presumably, combining this stack with the processor stack, both in hardware, is one of the benefits of a "Java chip.")
In order to make the method call, we really have to ask the JVM to do the call for us. Here are the three functions declared in interpreter.h that will make these calls:
HObject *execute_java_constructor(ExecEnv *, char *classname,
   ClassClass *cb, char *signature, ...);
long execute_java_dynamic_method(ExecEnv *, HObject *obj,
   char *method_name, char *signature, ...);
long execute_java_static_method(ExecEnv *, ClassClass *cb,
   char *method_name, char *signature, ...);
Each function serves a specific purpose and is described separately in the following section.

Calling a Java Constructor

Native language code can construct an instance of any Java class that is currently loaded. Here again, the native code is hooking directly into the implementation of Java. Therefore, the new object will behave exactly as it would if you had constructed it in Java code. The general form of a native language constructor is this:
hNewObject = (HClassName *)execute_java_constructor(NULL, 
    "ClassName", NULL, "ConstructorSignature", ...);
The first parameter here is the ExecEnvor exception environment that applies to the constructor call. You can safely pass a NULLhere, and the constructor will operate with the default exception environment. If you are using sophisticated exception handling in your native code, please see the section "Native Methods and Exceptions."
The interesting pieces of this call are shown in italics. First of all, in order for the native method to use the object that gets constructed, it must know the object's class. Usually, this comes from including a header file for that class. Only a few classes have headers distributed with the JDK: ClassLoader, String, Thread, and ThreadGroup. However, by using javah, you can create header files for whatever classes you need. (See Chapter 14 for details.) If you do not need to access the object's methods or instance variables, you can cast the return value from execute_java_constructorto an HObject pointer. HObjectis the handle to the base class Object.

Loading Java Classes from Native Language Code
If you need to load a class directly from native code, you can call the function:
int LoadFile(char *filename, char *directory, char *SourceHint);
It is safe to pass NULL for SourceHint. This function will cause the ClassLoader to find the file and load it into memory. Once the class is loaded, its static block is executed. Be aware that the static block can throw an exception.
The ClassLoader also provides native code for the DoImport function.
int DoImport(char *name, char *SourceHint);
This will behave exactly as if the Java code had contained an "import name;" line.
The next item of interest in this call is the class name. This is a text string that specifies what class is to be instantiated. This class must already be loaded, or reside on the class path. When you pass a class name, the Java runtime must perform a lookup on the class itself. However, if you have a pointer to the class itself, you can leave the class name argument NULLand pass the ClassClass pointer in the third argument. For example, if you wanted to clone an existing object of any class, you might use code like this:
struct HObject *Cloner_Clone(struct HCloner *hCloner,
                             struct HObject *hObj)
{
   HObject *hNewInst;
   hNewInst = execute_java_constructor(NULL, NULL, 
                 obj_classblock(hObject), "(A)", hObj);
   return hNewInst;
}
This native method can clone any object that has a copy constructor. The macro obj_classblockis another of the handle convenience macros. For any object, it will return a pointer to the class structure for that object. Many of the built-in functions of the Java runtime require a pointer to the class structure "ClassClass".
The constructor signature is used to select which constructor to invoke. It is a character string that specifies the number and type of arguments to follow. Table 19.2 shows the possible characters and their meanings.
Table 19.2. Signature characters and their meanings.

Character
Meaning
A
Any (object)
[
Array (object)
B
Byte
C
Char
L
Class
;
End class
E
Enumeration
F
Float
D
Double
(
Function argument list start
)
Function argument list end
I
Int
J
Long
S
Short
V
Void
Z
Boolean
By concatenating characters from this set, you specify what arguments are to follow. Two characters have special significance: the parentheses indicate the beginning and end of the argument list. For a constructor, parentheses should enclose the entire argument list. For other methods, the signature will also indicate the return type of the method. In the preceding example, the signature is "(A)", which means the constructor must simply take one argument-an object instance. This implies that our constructor could receive objects of other classes as arguments. If we knew the class name, say Foo, for example, we could write the signature as "(LFoo;)", which means the constructor must take an instance of class Fooor its subclasses, as an argument. Here is a revised version of the Clone method, which takes into account the class name of the argument passed in:
struct HObject *Cloner_Clone(struct HCloner *hCloner,
                             struct HObject *hObj)
{
   HObject *hNewInst;
   char     signature[80]; 

   sprintf(signature, "(L%s;)", classname(obj_classblock(hObj))); 
   hNewInst = execute_java_constructor(NULL, NULL, 
                 obj_classblock(hObject), signature, hObj);
   return hNewInst;
}
Notice that, although "name" is a simple member of struct ClassClass, we use the classname macro (from oobj.h) instead of a direct access. In general, you should never access a structure member directly. Instead, use one of the macros provided in oobj.hor interpreter.h (both included by StubPreamble.h). The macros shield you from future changes to the structures. What if there is no macro to access a particular member? Then you probably are not meant to use that member in the first place!
After the constructor signature, you can place a variable length argument list. These are the arguments that will be passed to your constructor. You should be careful to make sure that the type, number, and order of your arguments match the signature. Otherwise, your constructor may be called with garbage for arguments.
If there is an error while calling the constructor, execute_java_constructorwill return NULL.

Calling a Java Method

Now for the really good part. You can call any Java method from native code. It all works through the handle to the object instance. If the method to be called is static, see the next section. For dynamic methods-including final and native methods-use the execute_java_dynamic_methodfunction:
long execute_java_dynamic_method(ExecEnv *, HObject *obj,
    char *method_name, char *signature, ...);
The first parameter is the same as the first argument to execute_java_constructor-an exception environment. Again, it is safe to pass a NULLfor this parameter. If you require more sophisticated exception handling for the native code, see "Native Methods and Exceptions."
The second parameter is the object instance itself. This can be any valid object handle, whether it was passed from Java code as an argument or constructed in the native method itself.
The next parameter is the name of the method to be invoked. If this method does not exist in the object passed, execute_java_dynamic_methodwill throw an exception.
Finally, the fourth argument is the signature of the instance method. Again, the signature indicates the type and number of arguments to be passed to the instance method being called. This must be the same number and type of remaining arguments in the call to execute_java_dynamic_method.
The signature for a method differs slightly from the signature for a constructor. A method signature needs to indicate the expected return type. The signature can show this with an extra character after the closing parenthesis. For example, consider a method that takes arguments of two classes and a float and returns a byte:
public byte[] FunkyMethod(Object fc, SecondClass sc,
                          float difference);
This method would have the signature "(ALSecondClass;F)B". The call to execute_java_dynamic_methodwould then have three arguments after the signature: a generic object, an instance of SecondClass, and a float.
The return value from execute_java_dynamic_methoddepends on the method being invoked. It is declared as long, however, so you will need to cast it to the appropriate type. (Because long is wide enough to hold any primitive type or a handle, it is safe to cast it to whatever the method really returns.) Be careful, though. Because you are calling the method by a function, not directly, the compiler cannot possibly notify you of any changes in the method definition.

Calling a Static Java Method

Calling a class method from native code is similar to calling a dynamic method. Use the execute_java_static_methodfunction:
long execute_java_static_method(ExecEnv *, ClassClass *cb,
   char *method_name, char *signature, ...);
This is entirely analogous to the execute_java_dynamic_methodfunction, with one crucial difference. Instead of taking an object instance as a parameter, execute_java_static_methodrequires a class structure. This class structure can come from an object instance, using the obj_classblockmacro, or it can be retrieved using the FindClassfunction:
ClassClass* FindClass(ExecEnv *ee, char *classname, bool_t resolve);
FindClass will return a pointer to the class structure for any class name you pass in. This function may cause several things to happen. First, the named class may be loaded, which will in turn cause the named class's static block to execute. Exceptions may be thrown by FindClassitself, if it cannot find the class, or by the static block of the named class, when it is loaded.

Like the other runtime functions that can throw exceptions, FindClasscan take an exception environment as an argument. As usual, it is safe to pass a NULL here, in which case FindClass will use the current exception environment. 

No comments:

Post a Comment