Introduction
$doc
Although Android applications are mainly written by Java, developers sometimes may still want to leverage many good ready-made C/C++ libraries. The Android NDK is a toolset that lets you embed components that make use of native code in your Android applications.
Android applications run in the Dalvik virtual machine. The NDK allows you to implement parts of your applications using native-code languages such as C and C++. This can provide benefits to certain classes of applications, in the form of reuse of existing code and in some cases increased speed.
Before using NDK, you should first familiarize yourself with Java JNI programming because JNI is the prerequisites of programming NDK. In this first article of this small series, we will try to delve into the Java JNI inner workings and give elementary samples. In the subsequent article, we will explore Android NDK programming.
NOTEThe test environments we'll utilize in this series include:
1. Windows 7;
2. JDK (jdk-6u24-windows-i586.exe);
3. Android SDK 2.3r1;
4. Eclipse 3.6;
5. Android-NDK-R6b;
6. Cygwin 2.738;
7. Eclipse CDT (C/C++ Development Tooling, optional, at http://www.eclipse.org/cdt/);
8. Visual Studio 2010 (optional).
Introducing JNI
Similar to P/Invoke on the .NET Framework targeting enabling managed code to call native code, JNI is built serving as a bridge between Java and native code. With JNI, Java applications that use the JNI can incorporate native code written in languages such as C, C++, and Assemble, as well as code written in the Java programming language. In this way, JNI allows programmers to take advantage of the power of the Java platform, without having to abandon their investments in legacy code. The first part of Figure 1 illustrates the various cases of a Java application that uses JNI. In contrast, the second part shows the C based native code can connect with Java libraries, methods and classes through JNI.
Figure 1: JNI bridges Java app and a variety of native goodies

As is seen, JNI plays the role of setting up a bridge between Java apps and native code. Figure 2 indicates the relations among C based native code, JNI, and Java apps.
Figure 2: JNI serves as a gateway between native code and Java

Although JNI enables us to use tons of ready-made native libraries, it easily results in potential security risks because what the native code executes is the machine code it bears the right to utilize any resource of the host system. In another word, the native code is not limited by the execution environment.
Besides above, there are still some pitfalls that cannot be ignored. For example, subtle errors in the use of JNI may destabilize the entire JVM, an application that relies on JNI in some degree loses the platform portability Java offers. And also, the JNI framework does not provide any automatic garbage collection for non-JVM memory resources on the native side, etc.
Starting from the next paragraph, we are going to look into some typical cases of JNI programming and related notes.
First JNI Sample -Hello World
To make things clearer, I will detail into the steps to finish the first sample project.
Step 1: create the Java project and create the .java file
Start up Eclipse to create a simple Java project named JavaJNITest using the execution environment JavaSE-1.6. Next, create a new class named HelloWorld under the folder src. The final result looks like the following.
There are several points worth noticing above. First, we use the keyword native to declare the method displayHelloWorld as a native method. And further, this native method will be implemented in a native function library which will be loaded in the Java running time environment.
Next, we invoke the method loadLibrary of the static class System to load a native function library named hello. Whether the extension is .so or .dll depends upon the operation system type (in Linux being .so and in Windows .dll). And also, the keyword static identifies this library can only be loaded once. If for some reason the load fails, this method will throw a related exception.
Step 2: build the Java project to get the .class file
Now, click the menu item "Project"|"Build Project" to build the above project. In fact, you can use also the command below to achieve the same task:
As a result, you've got a byte code file named HelloWorld.class under the folder bin.
Step 3: generate the header file
In this step, we will generate the header file with the name same as the above class. To do this, we run the following command at the command line:
Note by default the header file is generated at the same folder as the file HelloWorld.class. Of course, we can also use the switch -o to change the target directory.
NOTEBy default javah.exe creates a header file for each class listed on the command line and puts the files in the current directory. Use the -stubs option to create source files. Use the -o option to concatenate the results for all listed classes into a single file.
The new native method interface, Java Native Interface (JNI), does not require header information or stub files. Javah.exe can still be used to generate native method function prototypes needed for JNI-style native methods. Javah.exe produces JNI-style output by default, and places the result in the .h file.
By default, when we install Java the corresponding path (in my case being "C:\Program Files\Java\jdk1.6.0_24\Bin") has been set up automatically for you.
Now, let's look at the content of the header file HelloWorld.h.
As you may have noticed, Java_HelloWorld_displayHelloWorld is the C function name that corresponds to the native method displayHelloWorld of the above Java class. And also, notice that the native method prototype contains two parameters, JNIEnv* and jobject, which are the MUST HAVE in JNI programming. The first parameter is a JNIEnv interface pointer, through which the native method can access the parameters and objects that a Java application passes in, while the second parameter of type jobject is the object itself. In some sense, it is similar to this pointer in a Java application.
Step 4: write the native method and generate the dll file
In this step, we will write the native method used above. To do this, you can use any C/C++ editor. In this case, I used Visual Studio 2010. The related details are as follows:
Launch Visual Studio 2010 to create a new Visual C++ CLR "Class Library" named hello, as shown in Figure 3 below.
Figure 3: Create a new Visual C++ CLR “Class Library”

Next, modify the header file Stdafx.h as follows:
Here we should let C++ compiler able to locate the header files path. To do this, in Visual Studio 2010, we can click the menu item "View"|"Other Windows"|"Property Manager" to open the Property Manager pane. And then, double click the file Microsoft.Cpp.Win32.user to open another dialog, as shown in Figure 4.
Figure 4: The new way in Visual Studio 2010 to set up the VC++ Directoires

Then from the "Include Directoies" position select "Edit" to open another dialog "Include Directoies" and add the following three paths required in our VC++ project (you may change the first parts of the paths in your case):
D:\2011-dotnetslackers-prj\JavaJNITest\bin
C:\Program Files\Java\jdk1.6.0_24\include\win32
C:\Program Files\Java\jdk1.6.0_24\include
Visual Studio will follow up the above paths to search for the required header files.
Now, let's open the main VC++ file hello.cpp to complete its final form, like the following:
Note in the native method we have to use the JNI function to convert the Java string into the native string. In this case, we use the method GetStringUTFChars to convert the Unicode string into a UTF-8 string. After the conversion, the result string can be used in most C/C++ functions, such as printf. In addition, after the UTF-8 string related operation is over we have to invoke another corresponding method ReleaseStringUTFChars to notify the virtual machine to release the UTF-8 occupied memory; or else, this many result in memory leak to run out of memory resources.
OK, now we can click the menu item "Build"|"Build hello" to build the project. If everything goes well, we can get the VC++ dynamic link library file named hello.dll.
Step 5: rebuild the Java project
Now let's return to Eclipse to reopen the previous Java project JavaJNITest to set up the Java native library location. To do this, click the menu "Project"- "Properties" to open the dialog as shown in Figure 5. From the left pane click "Java Build Path" and then click the "Source" tab on the right pane to open up the sub item "Native library location". Then click the button "Edit" to specify the path "D:/2011-dotnetslackers-prj/hello/Debug" (this path depends on your C++ library generated above) we built in the VC++ IDE.
Figure 5: Specify Native library location for the Java project

Till now, Eclipse can locate the native link library related position and run the Java sample application. Now, let's press Ctrl+F11 to run the current Java sample application JavaJNITest. Without any compile error, you will see the running-time result as shown in the Console window in Figure 6.
Figure 6: The running-time result of the first Java application

As is seen, the string "Hello world!" is output in the bottom Console window inside Eclipse.
Mapping Types Support
In this section we are going to delve into how to use the data types from Java in the native programming. This is commonly-required under the following cases:
- Use the passed-in arguments from Java in the native program;
- Invoke Java objects from native application;
- Native methods may return variables to its caller - Java applications.
First, the native method can directly call the incoming basic data types (such as boolean, integer, float, etc.) from Java apps. The following table shows the mapping of types between Java and native code.
Table 1: The mapping of basic data types between Java and native code
Java Language Type | Native Type | Description |
boolean | jboolean | 8, unsigned |
byte | jbyte | 8 |
char | jchar | 16, unsigned |
short | jshort | 16 |
int | jint | 32 |
long | jlong | 64 |
float | jfloat | 32 |
double | jdouble | 64 |
void | void | n/a |
Second, as is known the object types in Java are all transferred by reference, all of which are corresponding to jobject in native code. To facilitate usage and diminish errors, JNI defines some concrete data types that are conceptionally related to the sub classes of jobject. Refer to the following figure.
Figure 7: Native data types set

Access Java Applications from Native Methods
JNI provides a set of standard interface functions, through which we can create Java objects, access and handle them, release them, as well as invoke Java methods. This section will detail into how to use these functions.
Access Java string
Java application passes a string to native methods in the form of jstring. jstring is different from the string type defined in C/C++, so if you try to directly invoke the function printf to print the string, the Java virtual machine may crash. The following code illustrates the improper use of such a case.
As is used in the preceding example, the native methods have to use JNI functions to convert Java string to native string, and then they can use it. For example, we rest upon the two methods GetStringUTFChars and ReleaseStringChars of the interface pointer JNIEnv* to achieve the target of accessing Java string.
Access Java array
As with the jstring, you cannot access jarray directly in you native methods. Instead, you have to rely on the JNI provided interface functions. For instance, the following code is illegal:
Instead, you should use JNI specific functions to achieve the above target. In the following example the Java application will pass an integer array to the native method which adds up all the integers and returns the result to the invoker.
Listing 1: IntArray.java
The related C code is as follows:
Listing 2: IntArray.c
In the above code, we first use the JNI method GetArrayLength to get the length of the incoming array. And then, we invoke another JNI method GetIntArrayElements to obtain the pointer pointed to this array. Finally, we use the array (add up each element in the array) according to C syntax.
Access Java methods
JNI provides support for call back operations. In another word, JNI permits native code to invoke the methods (of a class or object) defined in a Java application.
On the whole, there are three steps needed to invoke a method of an object in Java:
1. Invoke the JNI function GetObjectClass to get the type of a Java object;
2. Invoke the JNI function GetMethodID to locate the method defined in a Java class according to the method identifier and name. If the method does not exist then return 0 and throw an exception NoSuchMethodError in Java app.
3. Invoke the JNI function CallVoidMethod to call the method with no returned values defined in Java. Note this function requires at least three arguments: object, method ID, and related parameters.
In the following example, the native code invokes the method defined in Java, and the method defined in Java in turn invokes the native method. When the recursion depth becomes 5 the Java method will no more call the native method and return.
Next, let's start up Visual Studio 2010 to create a VC++ based CLR library named MyImpOfCallbacks. The following gives the related C++ method to invoke the method defined in the above Java code.
As is shown above, the C++ native method callbacks the Java method callback of the Callbacks class, the whole process of which is consistent with the preceding steps we describe.
Another important point to be noticed is that JNI searches for symbols according to the method identifier and name. This can make sure that when there are new methods added into the Java class native code can still be used without any modification.
In addition, JNI, according to the method identifier, identifies the argument and return value type of the method. For instance, the above example uses (I)V to indicate that it wants to find a Java method having a integer-typed argument and no return value.
On the whole, the common form of the method identifier can be described as follows:
“(argument-type)return-type”
For more info concerning the related details, please refer to the reference materials supplied at the end of this article.
Now, let's look at the running-time result of the above sample.
Figure 8: The running-time snapshot related to the sample project Callbacks

Exception handling in native methods
In Java, when an exception is thrown out Java virtual machine will automatically invoke the corresponding exception handling code. Although some languages, such as C and C++, also provide the similar exception handling mechanism there are no consistent and standard means to handle such cases. For this, JNI produces its own functions to help native code throw Java exceptions. And further, it requires native methods to check the possible exceptions after the invocation of Java methods. Note that the exceptions thrown in native code not only can be handled in other part of the native method but also be dealt with in the Java application that calls the native method.
Let's consider an example. First, look at the Java code:
As usual, you should use javah.exe to generate the corresponding head file CatchThrow.h. Now, let's continue to look at the C++ code (contained in the VC++ sample class library named MyImpOfCatchThrow).
Obviously, the native call towards the method CallVoidMethod will surely trigger an exception NullPointerException being thrown out, so in the above code as the invocation of CallVoidMethod we call the JNI special method ExceptionOccurred to detect this exception. As is seen, in this case we've not made complicated management with this exception. Instead, we simply show a piece of debug info by calling the method ExceptionDescribe. And then, we call the method ThrowNew to throw out a new exception IllegalArgumentException which is just the exception to be detected by the Java application calling this native method.
Figure 9 illustrates the running result of the above sample.
Figure 9: The running-time snapshot related to the exception handling sample

A last word is except for the few functions, such as ExceptionOccurred, ExceptionDescribe, and ExceptionClear, you are suggested to check, handle and clear the possible exceptions when calling other JNI functions.
Multi-threading programming in native methods
As is known, multi-threading programming is supported in Java. Though, in the native methods you have to bear in mind that you should modify any global variable related value because other threads contained in the Java application invoking the native method may also use this global variable.
1. JNI
There are several points worth noticing when things involve multithreading in native methods.
(1) The JNI interface pointer (JNIEnv*) is only valid in the current thread - it cannot be passed to other threads or used in them. Though the same thread that invokes the native method in Java will pass the same interface pointer (JNIEnv*) to the native method, other threads will pass different interface pointers to the native method.
(2) You cannot pass a local reference to another thread. When a different thread wants to use the same reference of a Java object you should define it as a global reference.
(3) Carefully check the use of global variables. Because multiple threads may simultaneously access these global variables you have to prevent multiple threads from modifying the values of the global variables simultaneously.
2. Synchronization
In Java programs you can use the keyword synchronized to declare a segment of code, as follows:
Java virtual machine can ensure a thread, before running the above code, obtain the object obj related Monitor, so that the synchronized code can be run at most by one thread at anytime.
JNI provides two functions, i.e. MonitorEnter and MonitorExit, to make the code will be synchronously executed in the native methods. Their corresponding usage in the native methods is given below:
A thread, before executing the synchronized block, must enter the object obj related Monitor, and it can enter this Monitor for multiple times. Here the Monitor uses a counter to store the entering times of the special thread. As you may guess, when we invoke the method MonitorEnter the value of the counter will increase while invoking MonitorExit will decrease. Only the Monitor related counter value is equal to zero can other threads obtain the object obj related Monitor.
NOTEMonitor is an important concept in Java multi-threading programming. For details concerning this object, you can refer to this article or other ones. We are no more to delve into it.
3. Wait and Notify
There are also other several methods, such as Object.wait, Object.notify and Object.notifyAll, with which to achieve the synchronization of the multiple threads. A fact is JNI does not provide such methods related functions. Though, we can rest upon the approaches mentioned in the preceding paragraphs to invoke these methods in Java applications.
Summary
On the whole, this article has just scratched the surface of Java JNI related knowledge -there are still lots of good stuffs deserved to be further researched into. But this elementary tutorial is nearly enough for general NDK related programming under the Android environment. In the subsequent article, we will focus upon the really interesting Android NDK programming.
References:
1. Java Native Interface: Programmer's Guide and Specification.
2. Java Native Interface.
About Xianzhong Zhu
 |
I'm a college teacher and also a freelance developer and writer from WeiFang China, with more than fourteen years of experience in design, and development of various kinds of products and applications on Windows platform. My expertise is in Visual C++/Basic/C#, SQL Server 2000/2005/2008, PHP+MyS...
This author has published 81 articles on DotNetSlackers. View other articles or the complete profile here.
|
Please login to rate or to leave a comment.