Chapter 1 Introduction
The Java Native Interface (JNI) is a powerful feature in the Java platform. Programs written in JNI can call native code written in C/C++ as well as code written in JAVA. JNI allows programmers to use the benefits of JAVA without having to throw away previously written code. Because JNI is part of the JAVA platform, programmers can solve interoperability problems once and expect all their implementations to work on the JAVA platform.
This book serves as a programming guide for JNI, as well as a reference manual for JNI:
- Chapter 2 introduces JNI through a simple example, which is a tutorial for beginners unfamiliar with JNI
- Chapters 2 through 10 constitute a programmer’s guide that Outlines some of the JNI features. We’ll go through a series of short but meaningful examples to highlight features and introduce techniques that prove useful in JNI programming
- Chapters 11 through 13 cover the final specification for all JNI types and capabilities. These chapters can also be used as reference manuals
This book attempts to appeal to a wide audience of JNI’s diverse needs. While the textbook and programming guide are aimed primarily at novice programmers, experienced developers and JNI implementers may find the reference section more useful. The main audience for this book should be developers using JNI technology for application development. “You” in this book implicitly refers to developers who program using JNI, not to implementers of JNI or ultimate users of programs written using JNI.
This book assumes that you have a basic understanding of JAVA, C, and C++ programming languages. If not, you can check out one of the excellent reference books below: The Java™ Programming Language, Second Edition, by Ken Arnold and James Gosling (Addison-Wesley, 1998) The C Programming Language, Second Edition, by Brian Kernighan and Dennis Ritchie (Prentice Hall, 1988), And The C++ Programming Language, Third Edition, by Bjarne Stroustrup (addison-wesley, 1997).
1.1 Java Platform and Host Environment
Because the programs covered in this book are written in both the JAVA programming language and native programming languages (C/C++), let’s first clarify the scope of the programming environment for these languages.
The JAVA platform is a programming environment consisting of a JAVA virtual machine and a JAVA application programming interface. JAVA applications are written in the JAVA language and compiled to a binary class file format independent of extreme. Any virtual machine can execute this class file. Java application programming interfaces consist of a series of defined class files. Any implementation of the Java platform must guarantee support for the Java programming language, virtual machines, and application programming interfaces.
The host environment represents the host operating system, a set of local libraries, and the CPU instruction set. Native applications are written in native programming languages, such as C and C++, which are then compiled into host-specific binaries and linked to native libraries. Local applications and local libraries are usually associated with a specific host environment. For example, a C application built for one operating system will usually not run on other operating systems.
The Java platform is typically deployed on top of an operating environment. For example, the Java Runtime Environment (JRE) is a Sun product that supports the Java platform on existing operating systems such as Solaris and Windows. The Java platform provides the capability for a set of applications to be independent of the host environment.
1.2 JNI’s role
When the Java platform is deployed on top of a host environment, you may want or need to allow Java applications to work closely with native code written in other programming languages. Programmers have begun to adopt the Java platform to build applications traditionally written in C and C++, which will coexist with C/C++ code for years due to existing legacy code.
JNI is a powerful feature that allows code that leverages the Java platform but can also be written in other languages. As part of the JAVA Virtual Machine implementation, JNI is a bi-directional interface that allows JAVA applications to call native code and vice versa. Figure 1.1 illustrates this role of JNI:
JNI is designed to handle situations where Java applications and native code need to be combined. As a two-way interface, JNI can support two types of native code: native libraries and native applications.
- You can use JNI to write native methods and then allow Java applications to call methods implemented using native blocks. Java applications call native methods the same way they call methods written in the Java language. Behind the scenes, however, local methods are implemented in another language and reside in the local library.
- JNI supports an invocation interface that allows you to embed Java virtual machine implementations into native applications. Native applications can link to a native library that implements the Java Virtual Machine and then use interface calls to execute software components written in the Java programming language. For example, browsers written in C can run downloaded Applets that sneak into Java virtual machine implementations.
1.3 Significance of using JNI
Remember that once your program uses JNI, you lose two advantages that the Java platform provides.
First, Java applications that rely on JNI will no longer be able to run in multiple host environments. Although parts of the code written in the Java programming language can be ported to a host environment, it is necessary to recompile the parts written in the native language.
Second, while the Java programming language is type-safe and secure, native languages such as C/C++ are not. Therefore, you need to be very careful when writing applications using JNI. A misbehaving local method can cause the entire application to crash. Therefore, the Java application does a security check before invoking the JNI methods.
As a general rule, you should build your application so that native methods are defined in as few classes as possible. This requires a clearer separation between the native code and the rest of the application.
1.4 When to use JNI
Before starting to use JNI into a project, it is worth investigating step by step whether there is a better alternative. As mentioned in the previous section, applications using JNI have some disadvantages compared to applications written strictly in the Java programming language. For example, you lose the type safety that the Java programming language provides.
Many alternatives also allow Java applications to interoperate with code written in other languages, such as:
- A Java application may communicate with a local application over a TCP/IP connection or through some other interprocess communication (IPC) mechanism
- A Java application might connect to a traditional database through the JDBC API
- Java applications can take advantage of distributed object counting, such as the Java IDL API. A common feature of these alternatives is that the Java application and native code reside in different processes (and in some cases, on different machines). Process separation provides important benefits. The address space protection count provided by the process provides a high degree of fault isolation, so that a crashed local application does not immediately terminate the Java application with which it communicates over TCP/IP.
There are times, however, when you may find that your Java application needs to communicate with native code residing in the same process. This is where JNI counts come in handy. Consider the following scenario:
- The Java API may not provide the host-specific functionality required by the application. For example, an application may want to manipulate a particular file, but the Java platform does not provide API support, but manipulating the file through another process is cumbersome and inefficient.
- You may want to access existing local libraries without the additional overhead of copying and transferring data between different processes. Loading the local library in the same entry would be a funnier approach
- Quato-processed applications can result in unacceptable memory usage. This is often true if the inheritance resides on the same client. Loading a local library into an already existing application requires less system resources than starting a new program and then loading the local library into the program.
- You may want to implement a small amount of time-sensitive code in a lower-level language such as assembly. If a 3D-intensive application spends most of its time rendering graphics, you may find it necessary to use assembly code to write the core parts of the graphics library for best performance. In summary, if a Java application and native code must be in the same process, use JNI.
1.5 Evolution of JNI
Interoperability between Java applications and native code has been assured since the early days of the Java platform. The first version of the Java platform’s Java development component included a native method interface that allowed Java applications to call functions written in other languages, such as C and C++. Many third-party applications and implementations of Java class libraries (including, for example, java.lang, java.io, and Java.net) rely on native method interfaces to access functional environments in the underlying host.
Unfortunately, there were two major problems with the native method interface in the first JDK:
-
First, the native code accesses the fields in the object as members of the C structure. However, the Java Virtual Machine specification does not define how objects are laid out in memory. If a given Java virtual machine implementation lays out objects in a different way than the local method interface assumes, the local method library must be recompiled
-
Second, the local method interface in JDK version 1.0 relies on the conservative garbage collector because local methods can get Pointers directly to virtual machine objects. Any virtual machine implementation that uses a more advanced garbage collection algorithm cannot support the native method interface in JDK 1.0. JNI aims to overcome these problems. It is an interface that can be supported by all Java virtual machines in a variety of host environments. Through JNI:
-
Each virtual machine implementation can support more native code.
-
Development tool vendors do not have to deal with various native method interfaces
-
Most importantly, application programmers can write a version of native code that will run on different Java virtual machine implementations. JNI was first supported in JDK 1.1. Internally, however, JDK 1.1 still uses older types of native code to complete the Java programming interface. This is no longer the case in version 1.2 of the Java SDK, and native methods have been reworked to make them compliant with JNI standards.
JNI is a native interface supported by all Java virtual machine implementations. Starting with JDK 1.1, you should program to JNI. While the older native programming interfaces are still supported in version 1.2 of the Java SDK, they will not (or cannot) be supported in advanced Java virtual machine implementations in the future.
The Java SDK version 1.2 includes a number of JNI extensions. These extensions are backward compatible. The future development of JNI will maintain full binary compatibility.
1.6 Example Code
This book contains a number of sample programs that demonstrate the capabilities of JNI. Sample programs typically consist of multiple snippets of code written in the Java programming language, C, and C++. Sometimes these local generations refer to specific functions in Solaris and Win32 hosts. We also demonstrate how to build JNI programs using the JDK and command line versions of the Java SDK 2, such as Javah.
Keep in mind that the use of JNI is not limited to a specific host environment or a specific development tool. This book focuses on writing code rather than using tools to build and run it. The command-line tools bundled with the JDK and Java SDK 2 distributions are fairly primitive. Third-party functionality may provide an improved way to build JNI applications. You are encouraged to review the JNI documentation bundled with your choice of development tools, and you can download sample code from this book, as well as the latest updates to the book, at java.sun.com/docs/books/…
Chapter 2 Commencement
This chapter guides you through how to use the Java native interface. We’ll write a Java application that calls a C function to allow “Hello World! .
2.1 an overview of the
Figure 2.1 shows writing a calling C function using the JDK or Java SDK 2 distribution to print “Hello World!” Java application process. This process consists of the following steps:
- Create a class that declares a local method (helloWorld.java)
- Use javac to compile the HelloWorld source file, resulting in a helloWorld.class class file. The Javac compilation tool is provided with the JDK or Java 2 SDK distribution.
- Use Javah-jni to generate the C header file (helloworld.h) that contains the prototype of the local method function. The Javah tool is also shipped with the JDK or Java 2 SDK distribution.
- Write a C implementation of the local method (helloWorld.c)
- Compile the C implementation into a local library helloWorld.dll or libHelloWorld.so, using the C compiler and connectors available in the host environment
- Run the Hello World program using the Java runtime parser. Both the class file (helloWorld.class) and the local library (helloWorld.dll or helloWorld.so) are loaded at runtime. The rest of this chapter explains these steps in detail.
2.2 Defining local methods
First write the following program in the Java programming language. This program defines a class that contains the local method print with the class name HelloWorld.
class HelloWorld {
private native void print(a);
public static void main(String[] args) {
new HelloWorld().print();
}
static {
System.loadLibrary("HelloWorld"); }}Copy the code
The HelloWorld class definition starts by printing the declaration of the local method. Next, instantiate the HelloWorld class and call the print local method of this example. The last part of the class definition is a static initializer that loads the local library containing the local print method.
There are two differences between defining a local method such as print and defining a regular method using the Java programming language. A native method declaration must contain the native modifier. The native modifier indicates that the method is implemented by other programming languages. In addition, the local method declaration terminates with a semicolon (statement terminator) because the local method is not implemented in this class. We’ll write the print method in a separate C file.
The local library that implements print must be loaded before the local method print can be called. In this example, we load the local library in the static initialization block of the HelloWorld class. The Java virtual machine runs the code for statically initializing the block before calling any of the methods of the HelloWorld class, so you can be sure that the local library is loaded before the local method print is called.
We define a main method that can run the HelloWorld class. The helloWorld.main method calls the print local method in the same way as the regular method.
System.loadlibrary uses the library name, finds the local library associated with the library name, and loads the local library into the application. We will discuss the exact loading process later in this book. Now just remember that in order for System.loadLibrary(” HelloWorld “) to succeed, we need to create a helloWorld.dll file on win32, Create a libhelloworld. so file on your Solaris system.
2.3 Compile the HelloWorld class
After you finish writing the HelloWorld class, save the source code to a file called HelloWorld.java. Compile using JDK or javac tools included with Java SDK 2:
javac HelloWorld.java
Copy the code
This directive produces a helloWorld.class file in the current directory.
2.4 Creating headers for local methods
Next we will use the Javah tool to generate a JNI type header file, which will be useful later when using C to complete native methods. To execute javah, run the following commands:
javah -jni HelloWorld
Copy the code
The name of the header file is the class name followed by an “.h “ending. The above command generates a file named helloWorld.h. We will not list the contents of this header file here. The most important part of this file is the Java_HelloWorld_print function prototype, which is the C function that implements the helloWorld.print method:
JNIEXPORT void JNICALL Java_HelloWorld_print
(JNIEnv *, jobject);
Copy the code
Ignore the JNIEXPORT and JNICALL macros for now. You may have noticed that the C implementation of the local method accepts two arguments, even though the corresponding definition of the local method (defined in Helloworld.java) does not accept any arguments. The first argument to each local method implementation is a pointer to the JNIEnv interface. The second argument refers to the HelloWorld object itself, similar to the C++ this pointer. We’ll discuss how to use the JNIEnv interface pointer and the jobject parameter later in the book, but we’ll ignore them in this example.
2.5 Write native method implementations
JNI type header files generated using Javah can help you implement native methods using C/C++. The function you write must follow the function prototype in the generated header file. In the C file helloWorld.c, you can implement the helloWorld.print method as follows.
#include <jni.h>
#include <stdio.h>
#include <HelloWorld.h>
JNIEXPORT void JNICALL Java_HelloWorld_print(JNIEnv *env, jobject obj){
printf("Hello World! \n");
return ;
}
Copy the code
The implementation of this local method is very simple. It uses the printf function to display the string “Hello World! , and then return. As mentioned earlier, both JNIEnv Pointers and OBj object references are ignored.
This C program contains three header files:
- Jni.h: This header provides the information needed by the native code to call jNI functions. This file must always be included in C or C source files when writing native methods.
- Stdio.h: The code snippet above also includes stdio.h because it uses the printf function
- Helloworld.h: header generated by the Javah tool. It contains the function prototype for Java_HelloWorld_print.
2.6 Compile C source code and generate a local library
Remember that when you create a HelloWorld class in the helloWorld.java file, you include a line of code that loads the local library into the program:
System.loadLibrary("HelloWorld");
Copy the code
Now that all the necessary C source code has been written, you need to compile the helloworld.c file and create a local library.
Different operating systems provide different ways to create local libraries. On Solaris, the following command creates a dynamic library called libhelloworld.so.
cc -G -I/java/include -I/java/include/solaris HelloWorld.c -o libHelloWorld.so
Copy the code
The -g compilation option tells the C compiler to generate a dynamic library instead of a regular Solaris executable. In win32, the following instruction uses a dynamic link library (DLL) created by the Microsoft Visual C++ compiler, hellowold.dll
cl -Ic:\java\include -Ic:\java\include\win32 -MD -LD HelloWorld.c -FeHelloWorld.dll
Copy the code
The -md compilation option indicates helloworld.dll and win32 multithreaded C library links. The -ld compilation option indicates that the C compiler produces a DLL file instead of a regular Win32 executable. Of course, on Windows 32 or Solaris, you need to set the include path of the header file on your computer.
Note: I am using Ubuntu 16.04, the JDK version is OpenJDK 1.8, using the above instructions does not work, the following is the compilation instructions I use
cc -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -I. -fPIC -shared HelloWorld.c -o libHelloWorld.so
Copy the code
You can also use GCC, where JAVA_HOME is the path I configured to.bashrc:
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
Copy the code
You can configure it according to your own JAVA_HOME, and it will compile successfully.
2.7 Running programs
At this point, both main components to run the program are ready. The class file (hellowor.class) calls a local method that the local library (helloWorld.dll) implements. Because the HelloWorld class contains its own main method, on Solaris and Win32, this program can be executed as follows:
java HelloWorld
Copy the code
You can see the following output:
Hello World!Copy the code
In order for your application to run correctly, it is important to set up the path of the local library correctly. The local library path is a set of file directories that are searched when the Java VIRTUAL machine loads the local library. If you do not set the local library path correctly, you will see an error log similar to the following:
java.lang.UnsatisfiedLinkError: no HelloWorld in library path
at java.lang.Runtime.loadLibrary(Runtime.java)
at java.lang.System.loadLibrary(System.java)
at HelloWorld.main(HelloWorld.java)
Copy the code
You need to ensure that the local library is in one of the directories of the local library path you set. If you are running on a Solaris system, the LD_LIBRARY_PATH environment variable is used to set the local library path. Make sure that the path of the environment variable contains the directory where the dynamic library libhelloworld.so file is located. If the libhelloworld.so file is in the current directory, in a standard shell or KornShell, you can set the LD_LIBRARY_PATH environment variable with the following two commands
LD_LIBRARY_PATH=.
export LD_LIBRARY_PATH
Copy the code
The equivalent instructions in C Shell are as follows:
setenv LD_LIBRARY_PATH .
Copy the code
If you are running Windows 95 or Windows NT, make sure that helloWorld.dll is in the current directory, or that the directory it is in is already listed in the PATH environment variable.
In the Java 2 SDK 1.2 release, you can specify the path to the local library from the Java command line as follows:
java -Djava.library.path=. HelloWorld
Copy the code
The -d command-line option sets a Java platform property. Set java.library.path to “. , “.” Indicates that the Java virtual machine searches for local libraries in the current path.
Note: The blogger runs HelloWorld using the following command:
java -Djava.library.path=. HelloWorld
Copy the code
Chapter 3 Basic types, strings, and Arrays
One question programmers often ask when Java applications mix native programming language code is how data types in the Java programming language map to data types in native programming languages such as C/C++. The “Hello World! In the example, we don’t have any arguments to pass to the local method, and the local method doesn’t return any results to the caller. The local method simply prints a message and returns it.
In practice, many programs need to pass parameters to local methods and get return values from local methods. In this chapter, we’ll show you how to exchange data types between code written in the Java programming language and code written in the native programming language. We start with primitive types, such as integers, and common object types, such as strings and arrays. We’ll leave the full processing of arbitrary objects to the next chapter, where we’ll look at how local method code accesses fields and makes method calls.
3.1 A simple local method
Let’s start with a simple program that isn’t much different from the HelloWorld program from the previous chapter. The example program, prompt.java, contains a local method that prints a string, waits for user input, and finally returns the user’s input to the calling function. The code for this program is as follows:
class Prompt {
// native method that prints a prompt and reads a line
private native String getLine(String prompt);
public static void main(String args[]) {
Prompt p = new Prompt();
String input = p.getLine("Type a line: ");
System.out.println("User typed: " + input);
}
static {
System.loadLibrary("Prompt"); }}Copy the code
Prompt. Main calls the local method Prompt. GetLine to get input from the user. Call the System.loadLibrary method in the static initialization block to load the local library called Prompt into the program.
3.1.1 C prototype of local methods
The Prompt. GetLine method can be implemented using the following C functions:
JNIEXPORT jstring JNICALL Java_Prompt_getLine
(JNIEnv *, jobject, jstring);
Copy the code
You can use the Javah tool to generate a header file containing the above function prototypes. JNIEXPORT and JNICALL macros (both defined in files in jni.h) ensure that this function is exported from the local library and that the C compiler generates code using the proper calling convention for this function. C function names are formed by concatenating the “Java_” prefix, class name, and method name. Section 11.3 contains a more accurate description of how C function names are formed.
3.1.2 Local Method Parameters
As described in Section 2.4, local method implementations (such as Java_Prompt_getLine) accept two standard parameters in addition to those declared in the local method. The first argument is the JNIEnv interface pointer, which points to the position of the function table pointer. Pointers in each method table point to a JNI function. The local method always accesses the data structure in the Java virtual machine through one of the JNI functions. JNIEnv interface pointer as shown in Figure 3.1:
The second parameter depends on whether the local method is instance method static or instance method. The second parameter to instantiate a local method is the application of the method’s calling object, similar to the this pointer in C++. The second parameter to a static local method is the application to the class that defines the method. In our example, Java_Prompt_getLine is implemented as an instantiated local method. So the second argument, jobject, is a reference to the object itself.
3.1.3 Type Mapping
Parameter types in local method declarations have corresponding types in the native programming language. JNI defines a set of C/C++ types that correspond to types in the Java programming language. There are two types in the Java programming language: basic types, such as int, float, and CHAR, and reference types, such as classes, instances, and arrays. In the Java programming language strings are instantiations of the java.lang.String class.
JNI handles base and reference types differently. The mapping of primitive types is straightforward. The int type in the Java programming language is mapped to JINt in C/C++ (defined as a 32-bit signed integer in jni.h), and the float type in the Java programming language is mapped to jfloat in C/C++ (defined in jni.h, Is a 32-bit floating-point number. Section 12.1.1 contains all the basic types defined in JNI (here’s a quick screenshot, as shown below).
JNI passes objects to local methods as opaque references. Opaque references are C pointer types that refer to data types inside the Java virtual machine. The exact layout of the data inside the Java virtual machine is hidden from the programmer. Native code can manipulate the underlying objects through the appropriate functions pointed to by the JNIEnv interface pointer. For example, java.lang.String corresponds to jString of JNI type, and the exact location of a jString reference is independent of the native code. The native code uses jNI functions such as GetStringUTFChars to get the contents of the string.
All JNI types have the type jobject. For convenience and type safety, JNI defines A set of reference types that are conceptually subtypes of Jobject (A is A subtype of B, Then each instance of A will be an instance of B. These subtypes correspond to reference types commonly used in the Java programming language. For example, jString represents a string, jobjectArray represents object data, and section 12.1.2 (which also briefly shows the screenshot below) completely lists the relationships between JNI reference types and their subtypes.
3.2 Accessing strings
Java_Prompt_getLine The prompt parameter is a Jstring. The jString type represents a string in the Java virtual machine, but is different from regular C strings (Pointers to characters, char *). You cannot use jString as a regular C string. Running the following code will not yield the desired results and in fact will probably cause the Java virtual machine to crash.
JNIEXPORT jstring JNICALL
Java_Prompt_getLine(JNIEnv *env, jobject obj, jstring prompt) {
/* ERROR: incorrect use of jstring as a char* pointer */
printf("%s", prompt); . }Copy the code
3.2.1 Converting to a local String
Your native code must use the appropriate JNI functions to convert jString objects to C/C++ strings. JNI supports converting jStrings to Both Unicode and UTF-8 strings. Unicode strings use 16-bit values to represent characters, while UTF-8 strings use an encoding method that is upwardly compatible with 7-bit ASCII strings. Although utF-8 strings contain non-ASCII characters, they behave like C strings with NULL terminators. All 7-bit ASCII characters with values between 1 and 127 remain encoded in utF-8 strings. The highest bit in a byte is set, indicating the beginning of a 16-bit Unicode value for multi-byte encoding.
The Java_Prompt_getLine method calls the JNI method GetStringUTFChars to read the contents of the string. You can use the GetStringUTFChars method through the JNIEnv interface pointer. It converts jString references that are normally implemented as Unicode sequences in the Java Virtual machine to C-strings in UTF-8 format. If you were sure that the original string contained only seven ASCII characters, you could send the converted string to a C library function, such as printf (we’ll discuss processing non-ASCII strings in Section 8.2).
#include "Prompt.h"
#include <stdio.h>
JNIEXPORT jstring JNICALL Java_Prompt_getLine
(JNIEnv *env, jobject obj, jstring prompt)
{
char buf[128];
const jbyte *str;
str = (*env)->GetStringUTFChars(env, prompt, NULL);
if(str == NULL)
return NULL;
printf("%s", str);
(*env)->ReleaseStringUTFChars(env, prompt, str);
/* We assume here that the user does not type more than * 127 characters */
scanf("%s", buf);
return (*env)->NewStringUTF(env, buf);
}
Copy the code
Don’t forget to check the return value of GetStringUTFChars, because the Implementation of the Java virtual machine decides that memory needs to be allocated internally to hold utF-8 strings, and memory allocation may fail. If the memory request fails, GetStringUTFChars will return NULL and throw OutOfMemoryError. As we’ll see in Chapter 6, throwing an exception in JNI is different from throwing an exception in Java. A suspended exception thrown through JNI does not automatically change the control flow of native C code. Instead we need a displayed return statement to skip the rest of the C function. When Java_Prompt_getLine returns, the exception is returned to the caller of prompt. getLine in the function prompt. main.
3.2.2 Releasing local String Resources
ReleaseStringUTFChars is called when your native code has run out of utF-8 strings obtained by GetStringUTFChars. A call to ReleaseStringUTFChars indicates that the utF-8 string returned by GetStringUTFChars is no longer needed by the native code. A call to ReleaseStringUTFChars frees up the memory used by the UTF-8 string. Not calling ReleaseStringUTFChars to free memory will result in a memory leak and eventually run out of memory.
3.2.3 Creating a New String
You can create a new instance of java.lang.String in native code by calling the JNI function NewStringUTF. The NewStringUTF method takes a C-string in UTF-8 format as an argument and generates a java.lang.String instance object. The newly constructed java.lang.String instance has the same Unicode sequence as the given UTF-8 C-type String. NewStringUTF throws an OutOfMemoryError and returns NULL if the virtual machine cannot allocate enough memory to construct an instance of java.lang.String. In this case, we don’t need to check the return value because the native code will return immediately afterwards. If the NewStringUTF call fails, OutOfMemoryError is thrown in the method’s caller, Prompt. Main. If NewStringUTF is called successfully, it returns a reference to the newly constructed java.lang.String object. The newly constructed instance is returned in Prompt. GetLine and assigned to input in Prompt. Main.
3.2.4 Other JNI string methods
In addition to the GetStringUTFChars, ReleaseStringUTFChars, and NewStringUTF functions introduced earlier, there are other string-related methods supported in JNI. GetStringChars and ReleaseStringChars get string characters in Unicode format. These functions can be useful when the operating system supports Unicode as a native string format.
Utf-8 strings often end in ‘\0’, whereas Unicode strings do not. To count the number of Unicode characters in a JString reference, the JNI programmer can call GetStringLength. To count how many bytes a UTF-8 jString takes, we can call the ANSI C function strlen on the return value of GetStringUTFChars, Or call JNI GetStringUTFLength directly on the JString reference. The third argument to the GetStringUTFChars and GetStringChars methods requires a little extra explanation:
const jchar *GetStringChars(JNIEnv *env, jstring str, jboolean *isCopy);
Copy the code
When returning from the GetStringChars method, if the String returned is a copy of the characters in the original java.lang.String instance, the value of the memory address pointed to by isCopy is set to JNI_TURE. If the String returned is a direct reference to the character in the original java.lang.String instance, the value of the memory address pointed to by isCopy is set to JNI_FALSE. When the value of the memory address isCopy points to is set to JNI_FALSE, native code cannot modify the contents of the returned string. If you violate this rule, the original java.lang.String instance object will also be modified. This breaks the java.lang.String cannot be modified rule.
In most cases, NULL is passed to the method as an isCopy argument, because you don’t care whether the Java virtual machine returns a copy of the java.lang.String instance or a direct reference.
It is generally impossible to predict whether a virtual machine will copy the characters in a given java.lang.String. So programmers must be aware of the time and space overhead that functions such as GetStringChars may require to be proportional to the number of characters in an instance of java.lang.String. In a typical Java virtual machine implementation, the garbage collector relocates objects in the heap. Once a direct pointer to a java.lang.String instance is passed back to native code, the garbage collector cannot relocate the Java.lang.String instance. In other words, the virtual machine must fix the java.lang.String instance, because too much fixing can lead to memory fragmentation, so the virtual machine implementation is free to choose whether to copy the character or fix the instance for each GetStringChars call.
Don’t forget to call ReleaseStringChars when you no longer need to access the string elements returned by GetStringChars. ReleaseStringChars is mandatory regardless of whether isCopy in GetStringChars is set to JNI_FALSE or JNI_TRUE. ReleaseStringChars releases the copy or cancels the fixed instance, depending on whether GetStringChars returns a copy of the instance or a fixed instance.
3.2.5 JNI string functions added in Java 2 SDK 1.2
In order to increase the return to the Java virtual machine. Lang. The possibility of a character String instance pointer, the Java 2 SDK version 1.2 introduces a new set of Get/ReleaseStringCritical function. On the surface, they seem similar to the Get/ReleaseStringChars functions, which return a pointer to a character if possible; Otherwise, a copy will be made. However, there are significant limitations on how you can use these features.
You must treat the code in this pair of functions as if it were running in a critical region, where native code cannot be arbitrary. Calling a JNI function or any other local function that causes the current thread to block and wait for another thread in the Java virtual machine. For example, the current thread cannot wait for an I/O input stream from another thread. These restrictions allow the virtual machine to disable garbage collection when native code holds a direct pointer to a string element obtained through GetStringCritical. When the garbage collector is disabled, all other threads that trigger the garbage collector are suspended. Between the Get/ReleaseStringCritical to native code can’t call back to the cause of blocking calls, and create a new object. Otherwise, vm deadlocks may occur. Consider the following scenarios:
- Garbage collection triggered by another thread cannot occur until the current thread completes the blocking call and re-enables garbage collection.
- Meanwhile, the current thread cannot proceed because the blocking call requires the acquisition of a lock already held by another thread waiting to perform the garbage collection. Overlapping calls are safe for GetStringCritical and ReleaseStringCritical. Such as:
jchar *s1, *s2;
s1 = (*env)->GetStringCritical(env, jstr1); if (s1 == NULL) {.../* error handling */ }
s2 = (*env)->GetStringCritical(env, jstr2); if (s2 == NULL) { (*env)->ReleaseStringCritical(env, jstr1, s1); ./* error handling */}.../* use s1 and s2 */
(*env)->ReleaseStringCritical(env, jstr1, s1); (*env)->ReleaseStringCritical(env, jstr2, s2);
Copy the code
Get/ReleaseStringCritical to use does not require strict nested in the stack order. We can’t forget to check that it returns NULL because it’s out of memory, because GetStringCritical can still allocate a buffer and copy arrays if they represent different formats internally. For example, a Java virtual machine might not store arrays contiguously. In this case, GetStringCritical must copy all the characters in the JString instance in order to return the contiguous character array of the native code.
To avoid deadlocks, you should ensure that your native code should not arbitrarily call JNI functions after calling GetStringCritical and before calling ReleaseStringCritical. In the critical region is the only allowed JNI function nested call Get/ReleaseStringCritical and Get/ReleasePrimitiveArrayCritical.
JNI does not support the GetStringUTFCritical and ReleaseStringUTFCritical methods. Almost all of these functions require the virtual machine to create a copy of the string, because virtual machine implementations almost internally store strings in Unicode format.
The other functions added in the Java SDK 2 Release 1.2 are GetStringRegion and GetStringUTFRegion. These functions copy the string elements into a pre-allocated memory. The Prompt. GetLine method can be overridden using GetStringUTFRegion:
JNIEXPORT jstring JNICALL
Java_Prompt_getLine(JNIEnv *env, jobject obj, jstring prompt) {
/* assume the prompt string and user input has less than 128 characters */
char outbuf[128], inbuf[128];
int len = (*env)->GetStringLength(env, prompt);
(*env)->GetStringUTFRegion(env, prompt, 0, len, outbuf);
printf("%s", outbuf);
scanf("%s", inbuf);
return (*env)->NewStringUTF(env, inbuf);
}
Copy the code
GetStringUTFRegion takes as arguments the subscript and length at the beginning of the string, both of which are counted as Unicode characters. This function at the same time make border inspection, at the same time, it is necessary to sell StringIndexOutOfBoundsExecption anomalies. In the above code, we get the length from the string reference itself, so we can confirm that no subscript transgressions occur (but the above code lacks a check for prompt to make sure it is less than 128 characters long).
3.2.6 JNI String function Summary
&esmp; All string-related JNI functions are listed in Table 3.1, and the Java 2 SDK version 1.2 has added some new features to enhance the performance of certain string operations. In addition to improving performance, the added functionality does not support new operations.
Table 3.1 Summary of JNI string functions
JNI function | describe | Which version to start with |
---|---|---|
GetStringChars\ReleaseStringChars | Gets or releases a pointer to the Unicode formatted string content. A copy of the string may be returned. | JDK 1.1 |
GetStringUTFChars\ReleaseStringUTFChars | Gets or releases a pointer to the string content in UTF-8 format. A copy of the string may be returned | JDK 1.1 |
GetStringLength | Returns the number of Unicode characters in a string | JDK 1.1 |
GetStringUTFLength | Returns the number of bytes (excluding the mantissa 0) needed to represent the string in UTF-8 format. | JDK 1.1 |
NewString | Creates an instance of java.lang.String that has the same character sequence as the given Unicode format C String | JDK 1.1 |
NewStringUTF | Creates an instance of java.lang.String that has the same character sequence as the given UTF-8 format C String | JDK 1.1 |
GetStringCritical\ReleaseStringCritical | Gets a pointer to the Unicode formatted string content. A copy of the string may be returned. Native code can’t Get/ReleaseStringCritical call block in the middle | Java 2 JDK 1.2 |
GetStringRegion\SetStringRegion | Copies the contents of a string to or from a preallocated C buffer in Unicode format. | Java 2 JDK 1.2 |
GetStringUTFRegion\SetStringUTFRegion | Copies the contents of a string to or from a preallocated C buffer in UTF-8 format | Java 2 JDK 1.2 |
3.2.7 Select the appropriate string function
Figure 3.2 shows how programmers should select the appropriate string-related functions in JDK Release 1.1 and Java 2 SDK Release 1.2.
If you are using JDK 1.1 or 1.1 and 1.2 releases, so in addition to the Get/ReleaseStringChars and Get/ReleaseStringUTFChars without the rest of the options.
If you are programming using Java 2 JDK Release 1.2 or later and want to copy the contents of a string into an already-allocated C buffer, use GetStringRegion or GetStringUTFRegion.
For small, fixed-size strings, Get/SetStringRegion and Get/SetStringUTFRegion are almost always preferred functions, because the overhead of allocating C buffers on the C stack is minimal. The overhead of copying a few characters in a string is trivial.
One advantage of Get/SetStringRegion and Get/SetStringUTFRegion is that they do not perform memory allocation and therefore do not raise unexpected out-of-memory exceptions. If you ensure that no index overflow can occur, no exception checking is required. Another advantage of Get/SetStringRegion and Get/SetStringUTFRegion is that you can specify the starting index and the number of characters. These functions are appropriate if the native code only needs to access a subset of characters in a long string.
The GetStringCritical function must be used with great care. You must ensure that native code in the Java VIRTUAL machine does not create new objects or block calls that cause system deadlocks when holding a pointer returned by GetStringCritical.
Here is an example that illustrates the subtle issues that arise with GetStringCritical. The following code fetches the contents of the string and calls the fprintf function to write the character to the file handle fd:
/* This is not safe! * /
const char *c_str = (*env)->GetStringCritical(env, j_str, 0);
if (c_str == NULL) {.../* error handling */
}
fprintf(fd, "%s\n", c_str);
(*env)->ReleaseStringCritical(env, j_str, c_str);
Copy the code
The problem with the code above is that it is not always safe to write file handles when garbage collection is disabled by the current thread. Suppose, for example, that another thread T waits to be read from the fd file handle. Let’s further assume that the operating system buffer is set up in such a way that the fprintf call waits until thread T finishes reading all pending data from the FD. We have built up a possible deadlock scenario: if thread T cannot allocate enough memory as a buffer to read from a file handle, it must request garbage collection. Garbage collection requests are blocked until the current thread executes ReleaseStringCritical and until the fprintf call returns. However, the fprintf call is waiting for thread T to finish reading from the file handle.
The following code, while similar to the above example, is almost certainly deadlock-free:
/* This code segment is OK. */
const char *c_str = (*env)->GetStringCritical(env, j_str, 0);
if (c_str == NULL) {.../* error handling */
}
DrawString(c_str);
(*env)->ReleaseStringCritical(env, j_str, c_str);
Copy the code
DrawString is a system call that writes a string directly to the screen. Unless the screen shows that the driver is also a Java application running in the same virtual machine, the DrawString function will not prevent waiting indefinitely for garbage collection to occur.
In short, you need to consider a pair of Get/ReleaseStringCritical all possible blocking behavior between calls.
3.3 Accessing an Array
JNI treats arrays of primitive data types and arrays of objects differently. Basic data Types Arrays contain basic data types, such as int and Boolean. Object data contains elements of reference type, such as class instances or other arrays. For example, in code written in the Java programming language:
int[] iarr;
float[] farr;
Object[] oarr;
int[][] arr2;
Copy the code
Iarr and FARr are arrays of primitive data types, while OARr and ARR2 are arrays of objects.
The methods needed to access an array of primitive data types in native code are similar to the methods needed to access strings. Let’s look at a basic example. The following program calls the local method sumArray, which adds the contents of an int array.
class IntArray {
private native int sumArray(int[] arr);
public static void main(String[] args) {
IntArray p = new IntArray();
int arr[] = new int[10];
for (int i = 0; i < 10; i++) {
arr[i] = i;
}
int sum = p.sumArray(arr);
System.out.println("sum = " + sum);
}
static {
System.loadLibrary("IntArray"); }}Copy the code
3.3.1 Accessing arrays in C
Data is represented by a Jarray reference type and its “subtypes” such as jintArray. Just as JString is not a C-string, jarray is not C-data. You cannot access the Jarray reference directly to write the Java_IntArray_sumArray native method. The following C code is illegal and does not get the desired result:
/* This program is illegal! * /
JNIEXPORT jint JNICALL Java_IntArray_sumArray(JNIEnv *env, jobject obj, jintArray arr) {
int i, sum = 0;
for (i = 0; i < 10; i++) { sum += arr[i]; }}Copy the code
You should use the appropriate JNI functions to access elements in an array of primitive data types, as shown in the correct example below:
JNIEXPORT jint JNICALL Java_IntArray_sumArray(JNIEnv *env, jobject obj, jintArray arr) {
jint buf[10];
jint i, sum = 0;
(*env)->GetIntArrayRegion(env, arr, 0.10, buf);
for (i = 0; i < 10; i++) {
sum += buf[i];
}
return sum;
}
Copy the code
3.3.2 Accessing an Array of basic data types
The previous example used the GetIntArrayRegion function to copy all elements of an integer array into the C buffer. The third parameter is the starting index of the elements that need to be copied, and the fourth parameter represents the total number of elements that need to be copied. Once the elements are stored in the C buffer, we can access them in our native code. Exception checking is not needed because, in this example, we know that the array is 10 in length and therefore does not cause index out-of-bounds problems.
JNI supports the corresponding SetIntArrayRegion function, which allows native code to modify array elements of type int. Arrays of other primitive types (such as Boolean, short, and float) are also supported.
JNI supports a set of Get/Release<Type>ArrayElements. <Type>ArrayElements The same below) function (including the Get/ReleaseIntArrayElements) for example, allows the native code for direct Pointers to the original array element. Because the underlying garbage collector does not support fixation, the virtual machine may return a pointer to a copy of an array of primitive data types. We can use to rewrite in section 3.3.1 GetIntArrayElements native code realization function (including the Get/ReleaseIntArrayElements) for example, allows the native code for direct Pointers to the original array element. Because the underlying garbage collector does not support fixation, the virtual machine may return a pointer to a copy of an array of primitive data types. We can use GetIntArrayElements to override the native code implementation in Section 3.3.1:
JNIEXPORT jint JNICALL Java_IntArray_sumArray(JNIEnv *env, jobject obj, jintArray arr) {
jint *carr;
jint i, sum = 0;
carr = (*env)->GetIntArrayElements(env, arr, NULL);
if (carr == NULL) {
return 0; /* exception occurred */
}
for (i=0; i<10; i++) {
sum += carr[i];
}
(*env)->ReleaseIntArrayElements(env, arr, carr, 0);
return sum;
}
Copy the code
The GetArrayLength method returns the number of elements in an array of primitive data types or objects. When the array is first allocated, its length is fixed.
In the Java 2 SDK 1.2 Get/ReleasePrimitiveArrayCritical function are introduced. These functions allow the virtual machine to disable the garbage collector when native code accesses an array of primitive data types. Programmers pay attention to use these two functions must be the same with using the Get/ReleaseStringCritical function carefully. The Get/ReleasePrimitiveArrayCritical function for the native code can’t call the JNI method, cannot undertake blocking operations could lead to a deadlock.
3.3.3 Summary of Accessing arrays of Basic Data Types
Table 3.2 lists the related JNI methods for accessing basic data types. In Java 2 JDK version 1.2, a number of functions were added to increase the performance of certain array operations. The added functions do not provide new operations, only improvements to the performance of operations:
Table 3.2 Summary of accessing an array of basic data types
JNI function | describe | Which version to start with |
---|---|---|
Get<Type>ArrayRegion\Set<Type>ArrayRegion | Copies the contents of an array of primitive data types to a C buffer or copies the contents of a C buffer | JDK 1.1 |
Get<Type>ArrayElements\Release<Type>ArrayElements | Gets a pointer to the contents of an array of primitive data types, possibly returning a copy of that array JDK 1.1 | |
GetArrayLength | Returns the number of elements in an array | JDK 1.1 |
New<Type>Array | Creates an array of the given length | JDK 1.1 |
GetPrimitiveArrayCriticalReleasePrimitiveArrayCritical | Gets a pointer to the contents of an array of primitive data types, possibly disabling the garbage collector or returning a copy of the array | Java 2 JDK 1.2 |
3.3.4 Select the appropriate primitive array function
Figure 3.3 shows how, in JDK 1.1 and Java 2 JDK 1.2, programmers can choose the appropriate JNI functions to access arrays of primitive data types.
If you need to copy the contents of an array to or from a C buffer to an array, you should use the Get/Set<Type>ArrayRegion family functions. These functions will be the border inspection, and if necessary will be thrown ArrayIndexOutOfBoundsException anomalies. The local method implementation in Section 3.3.1 (p. 547) uses the GetIntArrayRegion method to copy 10 elements from a Jarray reference.
For small fixed-size arrays, Get/Set<Type>ArrayRegion is almost always the preferred function, because C buffers can be easily allocated from the C stack. The overhead of copying a few array elements is trivial.
The Get/Set<Type>ArrayRegion functions allow you to specify the starting index and number of elements, so they are preferred if the native code only needs to access a subset of the elements in a large array.
If no pre-allocated buffer, C is the size of the original array is uncertain, and the native code when holding a pointer to the array elements is not a blocking call, please use the Java 2 SDK version 1.2 of the Get/ReleasePrimitiveArrayCritical function. Just like the Get/ReleaseStringCritical function, must be very careful to use Get/ReleasePrimitiveArrayCritical function, in order to avoid deadlock.
It is always safe to use the Get/Release<Type>ArrayElements family of functions. The virtual machine can either return a direct pointer to an array element or a buffer holding a copy of the array element.
3.3.5 Accessing an Object Array
JNI provides a separate pair of functions to access an array of objects. The GetObjectArrayElement returns the element at the given index, while the SetObjectArrayElement updates the element at the given index. Unlike the case of primitive array types, you cannot get all object elements at once or copy multiple object elements. Strings and arrays are reference types, you can use the Get/SetObjectArrayElememt access to an array of strings and arrays of arrays.
The following code calls a local function to create a two-dimensional array of int, and then prints the contents of the array.
class ObjectArrayTest {
private static native int[][] initInt2DArray(int size);
public static void main(String[] args) {
int[][] i2arr = initInt2DArray(3);
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
System.out.print(""+ i2arr[i][j]); } System.out.println(); }}static {
System.loadLibrary("ObjectArrayTest"); }}Copy the code
The local method initInt2DArray creates a two-dimensional array based on the given size. The code for the local method to allocate and create a two-dimensional array might look like this:
JNIEXPORT jobjectArray JNICALL
Java_ObjectArrayTest_initInt2DArray(JNIEnv *env, jclass cls, int size)
{
jobjectArray result;
int i;
jclass intArrCls = (*env)->FindClass(env, "[I");
if (intArrCls == NULL) {
return NULL; /* exception thrown */
}
result = (*env)->NewObjectArray(env, size, intArrCls, NULL);
if (result == NULL) {
return NULL; /* out of memory error thrown */
}
for (i = 0; i < size; i++) {
jint tmp[256]; /* make sure it is large enough! * /
int j;
jintArray iarr = (*env)->NewIntArray(env, size);
if (iarr == NULL) {
return NULL; /* out of memory error thrown */
}
for (j = 0; j < size; j++) {
tmp[j] = i + j;
}
(*env)->SetIntArrayRegion(env, iarr, 0, size, tmp);
(*env)->SetObjectArrayElement(env, result, i, iarr);
(*env)->DeleteLocalRef(env, iarr);
}
return result;
}
Copy the code
The initInt2DArray method first calls the JNI function FindClass to get a reference to the element class of a two-dimensional array of int type. The FindClass argument “[I” is a JNI class descriptor for the type int[] in the Java programming language (Section 12.3.2). If the type query fails, FindClass returns NULL and throws an exception (for example, due to a lack of class files or out of memory).
Next, the NewObjectArray function allocates an array whose element type is determined by the intArrCls class application. NewObjectArray only allocates the first dimension, and we still need to fill in the array elements that make up the second dimension. There is no special data type in the Java virtual machine to represent multidimensional arrays. A two-dimensional array is really just an array.
The code for creating the two-dimensional array is straightforward. NewIntArray allocates individual array elements, and SetIntArrayRegion copies the contents of the TMP buffer into the newly allocated one-dimensional array. After completing the SetObjectArrayElement call, the JTH element of the i-th one-dimensional array has the value I +j. Executing the objectarrayTest. main method yields the following output:
0, 1, 2, 1, 2, 3, 2, 3, 4Copy the code
Calling DeleteLocalRef at the end of the loop ensures that the virtual machine does not run out of memory by holding JNI references such as iarr. Section 5.2.1 explains why and when DeleteLocalRef needs to be called.
Chapter 4 Fields and methods
Now that you know how JNI allows native code to access basic data types and reference types, such as strings and arrays, you need to learn how to interact with fields and methods of any object. In addition to accessing fields, this includes invoking methods written in the Java programming language in native code, often referred to as performing callbacks from native code.
We’ll start with JNI functions that support field access and method callbacks. Later in this chapter we will discuss simple but effective caching techniques to make these operations more efficient. At the end of this chapter, we’ll discuss the performance characteristics of calling local methods and accessing fields from local methods and performing callbacks.
4.1 Access Fields
The Java programming language supports two types of fields. Each instance object of a class has a separate copy of the instance fields of that class, and all instances of a class contribute static fields of that class. JNI provides methods that enable native code to get or set instance fields in an object and static fields in a class. Let’s start by looking at an example program that shows how the native code implementation accesses instance fields.
class InstanceFieldAccess {
private String s;
private native void accessField(a);
public static void main(String args[]) {
InstanceFieldAccess c = new InstanceFieldAccess();
c.s = "abc";
c.accessField();
System.out.println("In Java:");
System.out.println(" c.s = \"" + c.s + "\" ");
}
static {
System.loadLibrary("InstanceFieldAccess"); }}Copy the code
S InstanceFiledAccess class defines an instance field, the main method of creating a class a class object, set the instance fields, and then call native methods InstanceFiledAccess. AccessFiled. As we will see, the local method prints the current value of the instance field and then sets the value of the instance field to a new value. After the local method returns, we will print the value of the field again to demonstrate that the value of the field has indeed changed. Here is a local method InstanceFiledAccess. AccessField method concrete implementation:
JNIEXPORT void JNICALL
Java_InstanceFieldAccess_accessField(JNIEnv *env, jobject obj) {
jfieldID fid; /* store the field ID */
jstring jstr;
const char *str;
/* Get a reference to obj’s class */
jclass cls = (*env)->GetObjectClass(env, obj);
printf("In C:\n");
/* Look for the instance field s in cls */
fid = (*env)->GetFieldID(env, cls, "s"."Ljava/lang/String;");
if (fid == NULL) {
return; /* failed to find the field */
}
/* Read the instance field s */
jstr = (*env)->GetObjectField(env, obj, fid);
str = (*env)->GetStringUTFChars(env, jstr, NULL);
if (str == NULL) {
return; /* out of memory */
}
printf(" c.s = \"%s\"\n", str);
(*env)->ReleaseStringUTFChars(env, jstr, str);
/* Create a new string and overwrite the instance field */
jstr = (*env)->NewStringUTF(env, "123");
if (jstr == NULL) {
return; /* out of memory */
}
(*env)->SetObjectField(env, obj, fid, jstr);
}
Copy the code
Executing InstanceFiledAccess with the InstanceFieldAccess local library yields the following output:
In C:
c.s = "abc"
In Java:
c.s = "123"
Copy the code
4.1.1 Process of accessing instance fields
To access instance fields, local methods follow a two-step process. First, call GetFieldID to get the field ID from the class reference, field name, and field descriptor.
fid = (*env)->GetFieldID(env, cls, "s"."Ljava/lang/String;");
Copy the code
This sample code gets the class reference by calling GetObjectClass on the instance reference obj, which is passed to the local method implementation as the second argument.
Once you have the field ID, you can pass the object reference and field ID to the appropriate instance field accessors:
jstr = (*env)->GetObjectField(env, obj, fid);
Copy the code
Because strings and arrays are special types of objects, we use GetObjectField to access string instance fields. In addition to Get/SetObjectField, JNI supports other functions such as GetIntField and SetFloatField to access instance fields of primitive data types.
4.1.2 Field descriptors
You may have noticed in the previous section that we used the specially encoded C String “Ljava/lang/String” to represent instance fields in the Java programming language. These C strings are called JNI field descriptors.
The content of a string is determined by the declared fields. For example, use “I” for an int field, “F” for a float field, “D” for a double field, and “Z” for a Boolean field.
Field descriptors for reference types, such as java.lang.String, begin with the letter L, followed by the JNI class description field with a semicolon as the terminator. Fully qualify the “in the class name. The delimiter is changed to “/” in the JNI class descriptor, so the field descriptor you form for a java.lang.String field is: “Ljava/lang/String;” .
The array type descriptor contains the “[” character, followed by the array component type descriptor. For example,” [I “is the field descriptor for the int[] field type. Section 12.3.3 (image first) contains details about field descriptors and their Java programming language counterparts.
You can use the JavAP tool (shipped with the JDK or Java 2 SDK) to generate field descriptors from class files. Normally Javap prints the method and field types of a given class. If you use the -s option (and the -p option to display private members), Javap prints only the JNI descriptor.
javap -s -p InstanceFieldAccess
Copy the code
The above directive gives information about the JNI descriptor containing field S:
s Ljava/lang/String;
Copy the code
Using javap tools helps eliminate errors that can occur when manually exporting JNI descriptor strings.
4.1.3 Accessing static Fields
Let’s look at a small change to the InstanceFieldAccess example:
class StaticFielcdAccess {
private static int si;
private native void accessField(a);
public static void main(String args[]) {
StaticFieldAccess c = new StaticFieldAccess();
StaticFieldAccess.si = 100;
c.accessField();
System.out.println("In Java:");
System.out.println(" StaticFieldAccess.si = " + si);
}
static {
System.loadLibrary("StaticFieldAccess"); }}Copy the code
StaticFieldAccess class contains a static integer resource si, StaticFieldAccess. The main method to create an object, first initialize static field, and then call native methods StaticFieldAccess. AccessField. As we’ll see, the local method prints the current value of the static field, and then sets a new value for the static field. To verify that the value of the static field has really changed, print the value of the static field again after calling the static method.
Here is a static method StaticFieldAccess. AccessField implementation code:
JNIEXPORT void JNICALL
Java_StaticFieldAccess_accessField(JNIEnv *env, jobject obj) {
jfieldID fid; /* store the field ID */
jint si;
/* Get a reference to obj’s class */
jclass cls = (*env)->GetObjectClass(env, obj);
printf("In C:\n");
/* Look for the static field si in cls */
fid = (*env)->GetStaticFieldID(env, cls, "si"."I");
if (fid == NULL) {
return; /* field not found */
}
/* Access the static field si */
si = (*env)->GetStaticIntField(env, cls, fid);
printf(" StaticFieldAccess.si = %d\n", si);
(*env)->SetStaticIntField(env, cls, fid, 200);
}
Copy the code
Running the program using the local library produces the following output:
In C:
StaticFieldAccess.si = 100
In Java:
StaticFieldAccess.si = 200
Copy the code
There are two differences between how to access a static field and how to access an instance field:
- For static fields, you should call GetStaticFieldID, versus GetFieldID for instance fields. Both GetStaticFieldID and GetFieldID have the same return type as fieldID.
- Once you get the static field ID, you pass the class reference to the appropriate static field access function, and for instance fields you should pass the object reference.
4.2 Calling methods
In the Java programming language, there are several types of methods. Instance methods must be called from an instance of a particular class, whereas static methods can be called independently of any instance. We’ll discuss constructors in the next section.
JNI supports a complete set of functions that allow you to do callbacks in native code. The following example program contains local methods that, in turn, invoke instance methods implemented in the Java language.
class InstanceMethodCall {
private native void nativeMethod(a);
private void callback(a) {
System.out.println("In Java");
}
public static void main(String args[]) {
InstanceMethodCall c = new InstanceMethodCall();
c.nativeMethod();
}
static {
System.loadLibrary("InstanceMethodCall"); }}Copy the code
Here is the implementation of the native code:
JNIEXPORT void JNICALL
Java_InstanceMethodCall_nativeMethod(JNIEnv *env, jobject obj) {
jclass cls = (*env)->GetObjectClass(env, obj);
jmethodID mid = (*env)->GetMethodID(env, cls, "callback"."()V");
if (mid == NULL) {
return; /* method not found */
}
printf("In C\n");
(*env)->CallVoidMethod(env, obj, mid);
}
Copy the code
Running the above program yields the following output:
In C
In Java
Copy the code
4.2.1 Calling instance methods
The Java_InstanceMethodCall_nativeMethod method implementation indicates that there are two steps required to invoke an instance method:
- The local method first calls the JNI method GetMethodID. GetMethodID performs a method query on the given class. Queries are based on the name of the method and the type descriptor of the method. If this method does not exist, GetMethodID return NULL, at this point, from the local method returns immediately and will cause the call InstanceMethodCall. NativeMethod code throws NoSuchMethodError anomalies.
- The local method then calls CallVoidMethod. Ca llVoidMethod calls an instance method whose return type is void. You pass the object, the method ID, and the actual parameters (but in the example above, these are null) to the CallVoidMethod. In addition to the CallVoidMethod method, JNI supports method call functions of other return types. For example, if your callback method returns a value of type int, then your local method can use CallIntMethod. Similarly, you can use CallObjectMethod to call methods that return values as objects (including java.lang.String instances and arrays).
You can use the CallMethod family of functions to call interface functions. You must export the method ID from the interface type. The following code snippet calls the runnable. run method in a java.lang.Thread instance:
jobject thd = ... ;/* a java.lang.Thread instance */
jmethodID mid;
jclass runnableIntf =(*env)->FindClass(env, "java/lang/Runnable");
if (runnableIntf == NULL) {.../* error handling */
}
mid = (*env)->GetMethodID(env, runnableIntf, "run"."()V");
if (mid == NULL) {.../* error handling */} (*env)->CallVoidMethod(env, thd, mid); ./* check for possible exceptions */
Copy the code
We’ve already seen in Section 3.3.5 that FindClass returns a reference to an explicit class. Here we can also use it to get a reference to a named interface.
4.2.2 Generating method descriptors
JNI uses descriptor strings to represent method types, similar to how it represents field types. Method descriptors combine parameter types and return types. The parameter types appear first, enclosed by a pair of parentheses, and are listed in the order in which the method is declared. There are no delimiters between arguments, and if a method has no arguments, it is indicated by a pair of empty parentheses. Place the method return type after the close parenthesis of the parameter type.
For example, “(I)V” indicates that the method takes an argument of type int and returns type void. “()D” indicates that the method takes no arguments and returns a double. Don’t let a C function prototype like “int f(void)” mislead you into thinking that “(V)I” is a valid method descriptor; instead, use “()I” for its method descriptor. Method descriptors may contain class descriptors, such as the following methods:
native private String getLine(String);
Copy the code
The method descriptor is
(Ljava/lang/String;) Ljava/lang/String;Copy the code
Section 12.3.4 gives a complete description of how to build JNI method descriptors. You can print JNI method descriptors using the Javap tool. For example, run the following command:
javap -s -p InstanceMethodCall
Copy the code
You can get the following output:
private callback ()V
public static main ([Ljava/lang/String;)V
private native nativeMethod ()V
Copy the code
The -s flag tells JavAP to output JNI descriptor strings, rather than the types they appear in the Java programming language. The -p flag causes JavAP to include information about the private members of the class in its output
4.2.3 Calling static methods
The previous example demonstrates how to call an instance method in native code. Similarly, you can do static method callbacks from local methods by following these steps:
- Get the static method ID with GetStaticMethodID instead of GetMethodID
- Pass the class, method ID, and argument to one of the static method calling functions: CallStaticVoidMethod, CallStaticBooleanMethod, and so on. There is a key difference between a function that allows you to call static methods and a function that allows you to call instance methods. The former takes class references as arguments, while the latter takes object references as arguments. For example, pass the class reference to CallStaticVoidMethod, but pass the object reference to CallVoidMethod.
At the Java programming language level, you can use two optional syntax to invoke static methods f in class Cls: cls. f or obj.f, where obj is an instance of Cls. (The latter is the recommended programming style.) In JNI, class references must always be specified when static method calls are made from native code.
Let’s look at an example: using a callback in static code with a static method. It has some differences from the previous InstanceMethodCall:
class StaticMethodCall {
private native void nativeMethod(a);
private static void callback(a) {
System.out.println("In Java");
}
public static void main(String args[]) {
StaticMethodCall c = new StaticMethodCall();
c.nativeMethod();
}
static {
System.loadLibrary("StaticMethodCall"); }}Copy the code
Here is an implementation of the local methods:
JNIEXPORT void JNICALL
Java_StaticMethodCall_nativeMethod(JNIEnv *env, jobject obj) {
jclass cls = (*env)->GetObjectClass(env, obj);
jmethodID mid = (*env)->GetStaticMethodID(env, cls, "callback"."()V");
if (mid == NULL) {
return; /* method not found */
}
printf("In C\n");
(*env)->CallStaticVoidMethod(env, cls, mid);
}
Copy the code
Make sure you pass it to the CallStaticVoidMethod via CLS (highlighted in bold), not obj. The following results can be obtained by running the above program:
In C
In Java
Copy the code
4.2.4 Calling instance methods of the parent class
You can call instance methods defined in the parent class but overridden by the class in which the instance object resides. JNI provides a set of CallNonvirtualMethod methods for this purpose. To invoke instance methods defined in the superclass, do the following:
- Get the method ID from the superclass reference using GetMethodID instead of GetStaticMethodID
- To, super class, the object ID and one of a series of parameters to the virtual calls the function such as CallNonvirtualVoidMethod, CallNonvirtualBooleanMethod, etc. The chances that you’ll need to call instance methods of a superclass are relatively rare, and the tool is similar to using the following constructs in the Java programming language to call overridden superclass methods (such as f) :
super.f();
Copy the code
The CallNonvirtualVoidMethod method can also be used to call constructors, as described in the following section.
4.3 Calling the constructor
In JNI, constructors can be invoked using steps similar to those used to invoke instance methods. To get the constructor’s method ID, use “” as the method name and” V “as the return type in the method descriptor. You can then call the constructor by passing the method ID to a JNI function (such as NewObject). The following code implements the equivalent of the JNI function NewString, which creates a java.lang.String object from Unicode characters and stores it in a C buffer:
jstring MyNewString(JNIEnv *env, jchar *chars, jint len) {
jclass stringClass;
jmethodID cid;
jcharArray elemArr;
jstring result;
stringClass = (*env)->FindClass(env, "java/lang/String");
if (stringClass == NULL) {
return NULL; /* exception thrown */
}
/* Get the method ID for the String(char[]) constructor */
cid = (*env)->GetMethodID(env, stringClass, "<init>"."([C)V");
if (cid == NULL) {
return NULL; /* exception thrown */
}
/* Create a char[] that holds the string characters */
elemArr = (*env)->NewCharArray(env, len);
if (elemArr == NULL) {
return NULL; /* exception thrown */
}
(*env)->SetCharArrayRegion(env, elemArr, 0, len, chars);
/* Construct a java.lang.String object */
result = (*env)->NewObject(env, stringClass, cid, elemArr);
/* Free local references */
(*env)->DeleteLocalRef(env, elemArr);
(*env)->DeleteLocalRef(env, stringClass);
return result;
}
Copy the code
This example is complex and deserves careful analysis. First, FindClass returns a reference to the java.lang.String class. Next, GetMethodID returns the method ID of the String constructor (String(char[] chars)). We then call NewCharArray to allocate a character array to hold all the character elements. The JNI function calls the constructor specified by the method ID. NewObject takes as arguments a reference to the class to be constructed, the constructor’s method ID, and the parameters to be passed to the constructor.
The DeleteLocalRef call allows the VM to free local resources occupied by elemArr and stringClass. Section 5.2.1 provides a detailed description of when and why DeleteLocalRef needs to be called.
Strings are objects, which is further highlighted by this example. But this example also raises a question. Why does JNI provide functions like NewString when we can use other JIN functions to achieve equivalent functionality? This is because built-in String functions are more efficient than native code calling the Java.lang.String API. Because String is the most used object type, it deserves special support in JNI.
The constructor can also be called using the CallNonvirtualVoidMethod function. In this case, the native code must first create an initialized object through the AllocObject function. A single NewObject call above
result = (*env)->NewObject(env, stringClass, cid, elemArr);
Copy the code
Can be replaced by AllocObject followed by a CallNonvirtualVoidMethod.
result = (*env)->AllocObject(env, stringClass);
if (result) {
(*env)->CallNonvirtualVoidMethod(env, result, stringClass, cid, elemArr);
/* we need to check for possible exceptions */
if ((*env)->ExceptionCheck(env)) {
(*env)->DeleteLocalRef(env, result);
result = NULL; }}Copy the code
AllocObject creates an initialized object and must be used with care so that at most one constructor is called per object. Native code should not call the constructor more than once on the same object.
Sometimes you will find it useful to create an initialized object and then call the constructor. But in the more time you should call NewObject, and avoid using more prone to errors of AllocObject/CallNonvirtualVoidMethod methods.
4.4 Cache Fields and Method ids
Obtaining field and method ids requires symbolic lookups based on the field and method ID names and descriptors. Symbol lookups are relatively expensive, and in this section we introduce a technique that can reduce this overhead. This approach is to compute fields and method ids and then cache them for subsequent reuse. There are two ways to cache fields and method ids, depending on whether the cache is performed when words and method ids are used or when fields or methods are defined in a static initialization block.
4.4.1 Perform caching at use
Field or method IDS can be cached when local code accesses field values or performs method callbacks. Below Java_InstanceFieldAccess_accessField function implementation, using a static variable to cache method ID for each call InstanceFieldAccess. AccessField method, do not need to recalculate.
JNIEXPORT void JNICALL
Java_InstanceFieldAccess_accessField(JNIEnv *env, jobject obj) {
static jfieldID fid_s = NULL; /* cached field ID for s */
jclass cls = (*env)->GetObjectClass(env, obj);
jstring jstr;
const char *str;
if (fid_s == NULL) {
fid_s = (*env)->GetFieldID(env, cls, "s"."Ljava/lang/String;");
if (fid_s == NULL) {
return; /* exception already thrown */}}printf("In C:\n");
jstr = (*env)->GetObjectField(env, obj, fid_s);
str = (*env)->GetStringUTFChars(env, jstr, NULL);
if (str == NULL) {
return; /* out of memory */
}
printf(" c.s = \"%s\"\n", str);
(*env)->ReleaseStringUTFChars(env, jstr, str);
jstr = (*env)->NewStringUTF(env, "123");
if (jstr == NULL) {
return; /* out of memory */
}
(*env)->SetObjectField(env, obj, fid_s, jstr);
}
Copy the code
The static variable fid_s shown in bold holds the method ID precomputed for InstancefiledAccess.s. The static variables are initialized to NULL, when InstanceFieldAccess accessField method is invoked for the first time, it calculates the field ID if the cache to the static variables for subsequent use.
You may have noticed that there are obvious race conditions in the code above. Multiple threads may call InstanceFieldAccess. At the same time accessField method and calculation of the same field ids at the same time. One thread may overwrite the static variable fid_s calculated by another thread. Fortunately, although this condition of competition leads to repetitive work in multithreading, it is obviously harmless. The same field of the same class computed by multiple threads must have the same field ID.
With the above ideas in mind, we could also cache the method ID of the java.lang.String constructor at the beginning of the MyNewString example.
jstring
MyNewString(JNIEnv *env, jchar *chars, jint len) {
jclass stringClass;
jcharArray elemArr;
static jmethodID cid = NULL;
jstring result;
stringClass = (*env)->FindClass(env, "java/lang/String");
if (stringClass == NULL) {
return NULL; /* exception thrown */
}
/* Note that cid is a static variable */
if (cid == NULL) {
/* Get the method ID for the String constructor */
cid = (*env)->GetMethodID(env, stringClass, "<init>"."([C)V");
if (cid == NULL) {
return NULL; /* exception thrown */}}/* Create a char[] that holds the string characters */
elemArr = (*env)->NewCharArray(env, len);
if (elemArr == NULL) {
return NULL; /* exception thrown */
}
(*env)->SetCharArrayRegion(env, elemArr, 0, len, chars);
/* Construct a java.lang.String object */
result = (*env)->NewObject(env, stringClass, cid, elemArr);
/* Free local references */
(*env)->DeleteLocalRef(env, elemArr);
(*env)->DeleteLocalRef(env, stringClass);
return result;
}
Copy the code
When MyNewString is first called, we calculate the method ID for the java.lang.String constructor. The static variable CID highlighted in bold caches the result.
4.4.2 Performing caching in a class’s static initializer block
When we cache a field or method ID in use, we must introduce a persistence to insist that the field or method ID is cached. Not only does this have a slight performance impact on the “fast path” when the ID is already cached, but it can also lead to rework of caching and checking. For example, if multiple local methods all need to access the same field, then they need to compute and check the corresponding field ID. In many cases, it is more convenient to initialize the fields and method IDS required by the local methods before the program has a chance to call them. The virtual machine always executes the class’s static initializer before calling any method in the class. Therefore, the proper place for a field and method ID to be computed and cached is in the static initialization block of the class for that field and method ID. . For example, to cache InstanceMethodCall callback method ID, we introduced a new method for local initIDs, it consists of InstanceMethodCall class static initializers call:
class InstanceMethodCall {
<b>private static native void initIDs(a); </b>private native void nativeMethod(a);
private void callback(a) {
System.out.println("In Java");
}
public static void main(String args[]) {
InstanceMethodCall c = new InstanceMethodCall();
c.nativeMethod();
}
static {
System.loadLibrary("InstanceMethodCall"); <b>initIDs(); </b> } }Copy the code
Compared with section 4.2 of the original code, the program contains additional two lines (highlighted in bold), the realization of the initIDs simply to InstanceMethodCall callback methods and cache ID.
jmethodID MID_InstanceMethodCall_callback;
JNIEXPORT void JNICALL Java_InstanceMethodCall_initIDs(JNIEnv *env, jclass cls) {
MID_InstanceMethodCall_callback = (*env)->GetMethodID(env, cls, "callback"."()V");
}
Copy the code
In the InstanceMethodCall class, the virtual machine runs a static initialization block before executing any of the methods, such as nativeMethod or main. When the method ID has been cached in a global variable, InstanceMethodCall. Local implementation of nativeMethod method is no longer need to perform symbol lookup.
JNIEXPORT void JNICALL
Java_InstanceMethodCall_nativeMethod(JNIEnv *env, jobject obj) {
printf("In C\n");
(*env)->CallVoidMethod(env, obj, MID_InstanceMethodCall_callback);
}
Copy the code
4.4.3 Comparison between two methods of caching ID
If JNI programmers have no control over the source code of a class that defines fields and methods, caching ids at use is a reasonable solution. For example, in the MyNewString example, there is no way to insert a user-defined initIDs local method into the java.lang.String class to pre-calculate and cache the method ID of the java.lang.String constructor. Caching at use has a number of disadvantages compared to performing caching in a static initialization block that defines a class:
- As explained earlier, caching is done at use time, checks are required during fast path execution, and the same field and method ID may be repeatedly checked and initialized.
- Method and field ids are only valid until the class is unloaded. If you are caching field and method ids at run time, you must ensure that the defined class cannot be unloaded or reloaded as long as the native code still depends on the value of the cache ID. (The next chapter shows how to protect a class from being unloaded by creating a reference to it using JNI.) On the other hand, if caching is done in the static initialization block that defines the class, the ID of the cache is automatically recalculated when the class is unloaded and reloaded later. Therefore, it is best to cache the field and method ID in the static initialization block of the class it defines, whenever feasible.
4.5 Operational performance of JNI fields and methods
Now that you know how to cache field and method ids to improve performance, you may be wondering: What are the performance characteristics of accessing fields and calling methods using JNI? How does the cost of performing method callbacks from native code compare to the cost of calling local methods and calling regular methods? The answer to this question certainly depends on how efficiently the underlying virtual machine implements JNI. It is therefore impossible to give precise performance features that are guaranteed to be applicable to a wide variety of virtual machine implementations. Instead, we’ll examine the inherent costs of local method calls and JNI field and method operations without providing general performance guidelines for JNI programmers and implementers. Let’s start by comparing the cost of Java/ Native calls to Java/Java calls. Java/ Native calls may be slower than Java/Java calls for the following reasons:
- In a Java virtual machine implementation, local method calls most likely follow a different convention than Java/Java calls. Therefore, the virtual machine must perform additional operations to build the parameters and set up the stack structure before jumping to the local method entry.
- Virtual machines often use inline method calls. Inline Java/ Native calls are much more difficult than inline Java/Java calls. We estimate that a typical virtual machine implementation executes Java/ Native calls about two to three times slower than Java/Java calls. Because Java/Java calls only take a few cycles, the extra overhead is negligible unless the local method performs something trivial. It is possible to build a Java virtual machine implementation that has Java/ Native call performance close to or equal to Java/Java call performance. (For example, such a virtual machine could adjust the JNI invocation rules to be the same as the Java/Java invocation rules.)
The performance characteristics of Native /Java callbacks are technically similar to Java/ Native calls. In theory, native/Java callbacks can also be two to three times more expensive than Java/Java calls. But in practice, native/Java calls are relatively rare, and virtual machines generally don’t optimize for callback performance. At the time of this writing, many virtual machine implementations make native/Java callbacks 10 times more expensive than Java/Java calls.
The overhead of field access using JNI is primarily the cost of calls through JNIEnv. Instead of referring to objects directly, native code refers to objects through the return value of C calls. Function call because it insulates the native code from the internal implementation representation maintained by the virtual machine implementation. The overhead of JNI field access is negligible because function calls only take a few cycles.