/**************************************************************************** * * Native.cpp (for use with Sun's JDK version 1.1.5) * * This file implements the native routines declared in Native.java. The * code is commented and fairly straight-forward. Note that there is * almost no error checking in this code, however, a few functions do * implement some error checks as a way of demonstrating how and where * to check. * * This code was developed by Matthew Mead for CS510 Java Implementation. * A few of the examples were derived from Microsoft's documentation, * specifically, the countPrimes algorithm. * * Updated: * 12-18-98 Added Pass2DByteArray() * */ #include #include #include "NativeC.h" /**************************************************************************** * Java declaration: * native public void displayHelloWorld(); * * The canonical first method. * */ JNIEXPORT void JNICALL Java_Native_displayHelloWorld(JNIEnv *env, jobject obj) { printf("Hello world!\n"); return; } /**************************************************************************** * Java declaration: * native public void initializeByteArray(int Count, byte Value); * * Initializes an array 'Count' times with the value 'Value'. This function * is mainly used to test the performance of array accesses in native * code. * */ JNIEXPORT void JNICALL Java_Native_initializeByteArray(JNIEnv *env, jobject obj, jbyteArray byteArray, jint Count, jbyte Value) { // Get elements of the array jbyte *elements = env->GetByteArrayElements(byteArray, 0); // Get length of the array unsigned int len = env->GetArrayLength(byteArray); // Loop through the array 'Count' times, assigning 'Value' to each // element. for (long i = 0; i < Count; i++) for (unsigned long j = 0; j < len; j++) elements[j] = Value; } /**************************************************************************** * Java declaration: * native public void Pass2DByteArray(byte[][] array); * * Pass a 2-D array of bytes from Java. This method will retrieve each * element from the arrays, print it, then multiply it by 10 and store * the new value back into the array. This is to show access/updating * of 2-D arrays within native code. The process would be similar for * 3-D arrays. New 12-18-98 * */ JNIEXPORT void JNICALL Java_Native_pass2DByteArray(JNIEnv *env, jobject obj, jobjectArray array2D) { // Get length of the array (how many arrays?) unsigned int num_arrays = env->GetArrayLength(array2D); printf("In native method printing 2-D byte array.\n"); printf("Each element is then multiplied by 10 and updated.\n"); // Loop over each array for (int i = 0; i < num_arrays; i++) { // Get the object at the i'th position (it's an array of bytes) jbyteArray array = (jbyteArray) env->GetObjectArrayElement(array2D, i); // Get the length of this array of bytes unsigned int length = env->GetArrayLength(array); // Get the elements from the byte array jbyte *elements = env->GetByteArrayElements(array, 0); // Print each element, then multiply the value by 10 // and put it back into the array. (This is to prove that // the array elements have changed when this function returns // to Java code. for (int j = 0; j < length; j++) { printf("%i,%i = %i\n", i, j, elements[j]); // Update the element (just multiply it by 10) elements[j] = elements[j] * 10; } } } /**************************************************************************** * Java declaration: * native public jobjectArray returnRectArray(int size); * * This function creates an array of 'size' Rectangles and returns them * to the Java caller. First, the array is created, then each element * is assigned a Rectangle object. * */ JNIEXPORT jobjectArray JNICALL Java_Native_returnRectArray(JNIEnv *env, jobject obj, jint size) { // Get a handle on the Rectangle class jclass cls = env->FindClass("java/awt/Rectangle"); if (cls == 0) { printf("Can't FindClass(java/awt/Rectangle)\n"); return NULL; } // Get its constructor (the one that takes 4 integers) jmethodID mid = env->GetMethodID(cls, "", "(IIII)V"); if (mid == 0) { printf("Can't get MethodID for Rectangle constructor\n"); return NULL; } // Allocate the array of Rectangles jobjectArray array = env->NewObjectArray(size, cls, NULL); // Now initialize each one to a Rectangle for (int i = 0; i < size; i++) { // Create a new Rectangle object jobject element = env->NewObject(cls, mid, 0, 0, 5 * i, 10 * i); // Assign the Rectangle to an element of the array env->SetObjectArrayElement(array, i, element); } // Return whole array to caller return array; } /**************************************************************************** * Java declaration: * native public void printObjectArray(Object[] array); * * Given an array of Objects, each element is printed using the * 'toString' method of the Object. * */ JNIEXPORT void JNICALL Java_Native_printObjectArray(JNIEnv *env, jobject obj, jobjectArray objArray, jboolean Print) { // Get length of the array unsigned int len = env->GetArrayLength(objArray); // Make sure we have at least one object in the array if (len < 1) return; // Get an element of the array so we can get the right 'toString' method jobject ob = env->GetObjectArrayElement(objArray, 0); // Get the class associated with this object jclass cls = env->GetObjectClass(ob); // We will check this if (cls == 0) { printf("Can't GetObjectClass\n"); return; } // Get method ID for the 'toString' method of this object's class jmethodID mid = env->GetMethodID(cls, "toString", "()Ljava/lang/String;"); // We will check this also if (mid == 0) { printf("Can't GetMethodID for toString\n"); return; } // Loop the the array of objects and print out each one using // the 'toString' method of its ancestor class Object for (int i = 0; i < len; i++) { // Get the next element from the array jobject element = env->GetObjectArrayElement(objArray, i); // Call the 'toString' method, which returns a String representation of // Rectangle object. We must 'cast' the return value to a jstring // because the method returns a generic object. jstring s = (jstring) env->CallObjectMethod(element, mid); if (Print) { // Get C-style NULL-terminated string const char *buf = env->GetStringUTFChars(s, 0); // Print the C-string printf("%s\n", buf); // Release the buffer env->ReleaseStringUTFChars(s, buf); } } } /**************************************************************************** * Java declaration: * native public void VoidVoid(); * * This function takes no arguments and returns nothing. Just used to * demonstrate the most simplistic function call. Also used to time * function call overhead in the simplest case. * */ JNIEXPORT void JNICALL Java_Native_VoidVoid(JNIEnv *env, jobject obj) { return; } /**************************************************************************** * Java declaration: * native public String toStringWithPrint(); * * This is the traditional 'toString' method with a twist. It calls back * into Java to do the real work. There is a Native method called toString * which formats the object's members for printing. This function simply * calls it. * * The net effect is this: The Java object calls a native method which, * in turn, calls back to the Java object. The Java could have simply * made a call to it's own 'toString' method. This of course is for * demonstration purposes only. Don't do this at work!! * */ JNIEXPORT jstring JNICALL Java_Native_toStringWithPrint (JNIEnv *env, jobject obj) { // Get the class associated with this object jclass cls = env->GetObjectClass(obj); // The the ID for its 'String toString()' method jmethodID mid = env->GetMethodID(cls, "toString", "()Ljava/lang/String;"); // Call the method, which returns a String jstring s = (jstring)env->CallObjectMethod(obj, mid); // Get C-style NULL-terminated string const char *buf = env->GetStringUTFChars(s, 0); // Now, print the C-string here printf("%s\n", buf); // Release the buffer env->ReleaseStringUTFChars(s, buf); // Return the formatted string return s; } /**************************************************************************** * Java declaration: * native public void causeException(); * * Causes an exception in the JVM, but fails to catch it. Thus, it is * propagated back to the JVM. * */ JNIEXPORT void JNICALL Java_Native_causeException(JNIEnv *env, jobject obj) { jfieldID fid; jclass cls; jthrowable exception; // Get the class to which this object belongs cls = env->GetObjectClass(obj); // Attempt to get the ID of the 'nonexistent' member, which, not by // chance, doesn't exist! fid = env->GetFieldID(cls, "nonexistent", "Ljava/lang/String;"); // An exception has occurred, but it has not been suppressed. // The JVM will detect it and catch it. } /**************************************************************************** * Java declaration: * native public void handleException(); * * Causes an exception in the JVM, but detects it and supresses it * */ JNIEXPORT void JNICALL Java_Native_handleException(JNIEnv *env, jobject obj) { jfieldID fid; jclass cls; jthrowable exception; // Get the class to which this object belongs cls = env->GetObjectClass(obj); // Attempt to get the ID of the 'nonexistent' member, which, not by // chance, doesn't exist! fid = env->GetFieldID(cls, "nonexistent", "Ljava/lang/String;"); // Check for exception exception = env->ExceptionOccurred(); // exception is non-zero if an exception occurred if (exception) { // Just display a message printf("Exception handled in Main.cpp: %i\n", exception); // Clear the exception so the JVM doesn't react to it env->ExceptionClear(); // If you want to display a message about the exception, // in addition to clearing it, you call DescribeException. // This call will print out a description of the exception // and then clear the exception // // env->DescribeException(); } } /**************************************************************************** * Java declaration: * native public void printWXYZ(); * * Prints out four members of the Native object, w, x, y, and z. This * function acts sort of like the traditional 'toString' method in Java. * The members are declared in Native.java as such: * * public String w; // public Object * public int x; // public * private int y; // private (no protection here) * public static int z; // public static * * The return value from each GetFieldID call should be checked. * I don't check because I'm trying to keep the focus on the calls. */ JNIEXPORT void JNICALL Java_Native_printWXYZ(JNIEnv *env, jobject obj) { jint x, y, z; jstring w; jfieldID fid; jclass cls; // Get the class that obj belongs to cls = env->GetObjectClass(obj); // w is a String fid = env->GetFieldID(cls, "w", "Ljava/lang/String;"); // Get the Object (String) w. Note the cast to a jstring object. // GetObject returns a jobject. C++ wants the cast. w = (jstring) env->GetObjectField(obj, fid); // x is a non-static public field fid = env->GetFieldID(cls, "x", "I"); // Get the int x = env->GetIntField(obj, fid); // y is a non-static private field, same as public fid = env->GetFieldID(cls, "y", "I"); // Get the int y = env->GetIntField(obj, fid); // z is a static public field, so call different method fid = env->GetStaticFieldID(cls, "z", "I"); // Get static field z = env->GetStaticIntField(cls, fid); // Convert Java string into char array for C++ const char *buf = env->GetStringUTFChars(w, 0); // Sort of like the traditional 'toString' output printf("[w = %s, x = %li, y = %li, z = %li]\n", buf, x, y, z); // Prevent memory leaks env->ReleaseStringUTFChars(w, buf); } /**************************************************************************** * Java declaration: * native public String printLine(String text); * * Prints a Java string, and returns a Java string * */ JNIEXPORT jstring JNICALL Java_Native_printLine(JNIEnv *env, jobject obj, jstring string) { // Get the characters from the jstring const char *str = env->GetStringUTFChars(string, 0); // Print them to the screen printf("In Native code, string is: %s\n", str); // Release memory (required or else memory leaks) env->ReleaseStringUTFChars(string, str); // Create a new string and return it return env->NewStringUTF("String from Native code"); } /**************************************************************************** * This is a sample algorithm that I got from Microsoft's documentation. * I used it as the initial test case. I may have modified it slightly. * * The interesting point in this example is that the function is declared * as 'static' in the Java code. The only difference at the native level * is that the second parameter is a 'jclass' instead of a 'jobject'. * Most functions are non-static, and so require a handle to the object * rather than the class. * * Java decl: native public static int countPrimes(byte[] array); */ JNIEXPORT jint JNICALL Java_Native_countPrimes(JNIEnv *env, jclass cls, jbyteArray array) { unsigned long count = 0; unsigned long i, len; // Get the length of the array len = env->GetArrayLength(array); // Get a pointer to the elements of the array. Only primitive elements // can be accessed this way. jbyte *body = env->GetByteArrayElements(array, 0); // Initialize all elements for (i = 0; i < len; i++) body[i] = 1; // Set each element at a non-prime index to 0 for (i = 2; i < len; i++) { if (body[i] != 0) { unsigned long k; for (k = i + i; k < len; k += i) body[k] = 0; count++; } } // The number of primes between 1 and array.length return count; } /**************************************************************************** * A private native function that calls back into Java. Calls a void * function just for timings. * */ JNIEXPORT void JNICALL Java_Native_callbackVoid(JNIEnv *env, jobject obj, jint Count) { // Get the class that this object belongs to jclass cls = env->GetObjectClass(obj); // Get the method ID for 'static void printText(String)' jmethodID mid = env->GetMethodID(cls, "voidFunc", "()V"); // If we can't find the ID, just return. This kind of check should be in all // functions in this code, but it isn't (for brevity) if (mid == 0) return; for (jint i = 0; i < Count; i++) env->CallVoidMethod(obj, mid); } /**************************************************************************** * A private native function that calls back into Java to print a string. * * This function was not called from Java, but rather, it was called from * within this source file by another function. Just shows that the JNIEnv * and jobject parameters can be passed around to other functions while the * native code is active. * * The callback is a static method, but it could be non-static */ void printIt(JNIEnv *env, jobject obj, const char *string) { // Get the class that this object belongs to jclass cls = env->GetObjectClass(obj); // Get the method ID for 'static void printText(String)' jmethodID mid = env->GetStaticMethodID(cls, "printText", "(Ljava/lang/String;)V"); // If we can't find the ID, just return. This kind of check should be in all // functions in this code, but it isn't (for brevity) if (mid == 0) return; // Create a new Java string from the char * jstring str = env->NewStringUTF(string); // Call the static method env->CallStaticVoidMethod(cls, mid, str); } // Code that was removed during development /* JNIEXPORT void JNICALL Java_Native_cachedReference(JNIEnv *env, jobject obj) { jstring w; if (M_cls == 0) { M_cls = env->GetObjectClass(obj); if (M_cls == 0) { printf("M_cls = 0\n"); return; } M_fid = env->GetFieldID(M_cls, "w", "Ljava/lang/String;"); } w = (jstring) env->GetObjectField(obj, M_fid); const char *buf = env->GetStringUTFChars(w, 0); printf("[w = %s]\n", buf); env->ReleaseStringUTFChars(w, buf); } // printIt(env, obj, "This is printed via Java static from C++"); // C JNIEXPORT jstring JNICALL Java_Native_printLine(JNIEnv *env, jobject obj, jstring string) { char buf[128]; const char *str = (*env)->GetStringUTFChars(env, prompt, 0); printf("In Native code, string is: %s\n", str); (*env)->ReleaseStringUTFChars(string, str); return (*env)->NewStringUTF(env, "Replaced in Native code"); } */