/**
* Native Main Loop *
* @param argv * @return */
public static native int LibMain(String[] argv);
/**
* This fires on messages from the C layer *
* @param text */
@SuppressWarnings("unused")
private static void OnMessage(String text, int level) {
System.out.println("OnMessage text:" + text + " level=" + level);
} }
Natives.LibMain requires a native implementation. On the other hand, OnMessage (which is invoked from C) simply prints the message to standard output. With this in mind, let’s take a look at the native code.
Native Library
Here 11.330is where all the work should take place. We start with the implementation of the actual library lib.c (see Listing 2-3). This file lives in the native folder within the project folder.
■ Note Native libraries in Linux (also known as shared objects) are the equivalents of dynamic link libraries (DLLs)
in Windows. By convention, shared objects are named as
lib<NAME><VERSION>.so.
38
Listing 2-3. Native Library Implementation
#include <stdio.h>
#include <stdlib.h>
/* JNI Includes */
#include <jni.h>
#include "include/jni_Natives.h"
#define CB_CLASS "jni/Natives"
/**
* OnMessage callback */
#define CB_CLASS_MSG_CB "OnMessage"
#define CB_CLASS_MSG_SIG "(Ljava/lang/String;I)V"
// prototypes // Lib main Sub
int lib_main(int argc, char **argv) ; // Used to get the len of a Java Array
const int getArrayLen(JNIEnv * env, jobjectArray jarray);
// printf str messages back to java void jni_printf(char *format, ...);
// Global env ref (for callbacks) static JavaVM *g_VM;
// Global Reference to the native Java class jni.Natives.java static jclass jNativesCls;
/*
* Class: jni_Natives * Method: LibMain
* Signature: ([Ljava/lang/String;)V */
JNIEXPORT jint JNICALL Java_jni_Natives_LibMain (JNIEnv * env, jclass class, jobjectArray jargv) {
// Obtain a global ref to the caller jclass (*env)->GetJavaVM(env, &g_VM);
// Extract char ** args from Java array jsize clen = getArrayLen(env, jargv);
char * args[(int)clen];
39
jrow = (jstring)(*env)->GetObjectArrayElement(env, jargv, i);
const char *row = (*env)->GetStringUTFChars(env, jrow, 0);
(*env)->ReleaseStringUTFChars(env, jrow, row);
} /*
* Load the jni.Natives class */
jNativesCls = (*env)->FindClass(env, CB_CLASS);
if ( jNativesCls == 0 ) {
static void jni_send_str( const char * text, int level) { JNIEnv *env;
if ( !g_VM) {
printf("I_JNI-NOVM: %s\n", text);
return;
}
(*g_VM)->AttachCurrentThread (g_VM, (void **) &env, NULL);
// Load jni.Natives if missing
40
// Call jni.Natives.OnMessage(String, int) if (! mSendStr ) {
// Get aref to the static method: jni.Natives.OnMessage mSendStr = (*env)->GetStaticMethodID(env, jNativesCls , CB_CLASS_MSG_CB
, CB_CLASS_MSG_SIG);
}
if (mSendStr) { // Call method
(*env)->CallStaticVoidMethod(env, jNativesCls , mSendStr
* does a varargs printf into a temp buffer * and calls jni_sebd_str
*/
vsprintf (string, format,argptr);
va_end (argptr);
const int getArrayLen(JNIEnv * env, jobjectArray jarray) {
41
return (*env)->GetArrayLength(env, jarray);} /**
* Library main sub */
int lib_main(int argc, char **argv) {
int i;
jni_printf("Entering LIB MAIN");
for ( i = 0 ; i < argc ; i++ ) {
jni_printf("Lib Main argv[%d]=%s", i, argv[i]);
}
return 0;
}
Let’s dissect this file to understand what it does. Any C/C++ program that plans to do JNI calls must include the header file:
#include <jni.h>
This header file has the prototypes for all the JNI system calls to be used by your library. It can be found in your system’s Java home under JAVA_HOME/include, with extra Linux dependencies under JAVA_HOME/include/linux. At compile time, these paths must be included using -I$JAVA_HOME/include and -I$JAVA_HOME/include/linux in the Makefile (The agcc script you created in Chapter 1 will take care of all this).
Next, it includes the jni_Natives header file:
#include "include/jni_Natives.h"
This file contains the user-defined JNI prototypes for all native methods defined in the jni.Natives class. It is machine-generated and must not be edited by the user. The actual generation will be set up in the Makefile. To generate this file manually, the following command can be used:
javah -cp ../bin -d include jni.Natives
Here, javah is the Java Virtual Machine (JVM) command to generate native header files from Java classes, -cp defines the class path search path, -d include tells javah to save the file in the include folder (creating it if required), and jni.Natives is the Java class name from which you wish to extract the headers.
Next, the following constants are defined:
#define CB_CLASS "jni/Natives"
#define CB_CLASS_MSG_CB "OnMessage"
#define CB_CLASS_MSG_SIG "(Ljava/lang/String;I)V"
CB_CLASS is the name of the Java class that will be invoked within C (note that the period separating path names is replaced by /). CB_CLASS_MSG_CB is the name of the Java method (OnMessage) that will be
42
invoked (see Listing 2-2). CB_CLASS_MSG_SIG is a critical constant that defines the Java signature of the OnMessage Java method. Let’s take a closer look at this signature:
(Ljava/lang/String;I)V
A Java method signature has the format (ARGUMENTS)RETURN_TYPE, where the arguments can be encoded as follows:
I = Integer B = Byte S = Short C = Char
LJava_Class; = For Java classes enclosed by : L and ;
In our case, the first argument is a Java string (Ljava/lang/String;), and the second is an integer (I).
Note that all arguments are defined by a single character (except for classes that are enclosed by L;), and there are no separators between them. Finally, V is the return type defined as void.
■ Caution Method signatures are a major pain when coding in JNI. Any mistake in this string, and the library will
not be able to find the method at runtime.
Next, the file defines the prototypes for the functions within the library:
• int lib_main(int argc, char **argv): This is the entry point to the library. It receives the number of arguments (argc) and a list of arguments (argv), similar to the standard C main() function.
• int getArrayLen(JNIEnv * env, jobjectArray jarray): This function is used to get the length of a Java array, which will be translated into a C array for use by the library.
• void jni_printf(char *format, ...): This function is used by the library to send a text message back to Java. Note that ... indicates that the function will receive a vector of arguments.
Finally, we need two global references:
static JavaVM *g_VM;
static jclass jNativesCls;
g_VM is a reference to the JVM, and it will be used make JNI system calls. jNativesCls is a reference to the jni.Natives Java class used to invoke the Java method OnMessage. Note that the static keyword tells the compiler that these variables should be visible only within code in lib.c.