People.uwplatt.edu



Foreign Function Interface: JNIJustin Catterson AbstractMost languages contain a foreign function interface. The purpose of this is to reuse libraries that other languages have already established, access legacy code written in other languages, and to give programmers the ability to use multiple languages for a program to achieve better performance. Java is a preferred programming language because it is simple to write and has a large library, but performance issues and code that have proven the test of time has prevented Java from becoming the language of choice. The Java Native Interface (JNI) was developed to resolve these problems. My paper will discuss when the JNI should be used, some examples that explain how it works, problems with the JNI, typical coding errors that may occur while programming, and possible applications.IntroductionEarly in the development stages of Java, it was realized that Java would need a way to interface with native languages. The first release of Java’s Development Kit (JDK 1.0) included a way to communicate with C and C++. Libraries such as “Java.lang”, “Java.io”, and “” all used the native interface to communicate with the host operating system. The original foreign function interface did not take into account how different Java Virtual Machines (JVM) handle memory and different garbage collection schemes. The Java Native Interface (JNI) was developed in 1997 with the goal of being compatible across all JVM’s. Other concerns while developing the JNI was to be as efficient as possible, to allow the support of time-critical systems, and allow Java Virtual Machine’s to be embedded into native languages. The JNI has become the standard for communicating with native interfaces in Java, and all libraries that communicate with native languages have been re-written to follow the JNI standard. Figure 1 shows where the JNI fits into the Java Virtual Machine, if Java calls a native functions, JNI calls the dynamically linked library that contains the native method. If the native application is calling a library in Java, JNI will allow the native application to create a Java Virtual Machine.Figure 1: How JNI communicates with both native apps and the JVMDetermining to use the JNIBefore deciding you are going to use the JNI, you should first examine your other options. Using the JNI will add complexity to your program; it will make your program more difficult to debug, you will also have to consider memory leaks, you will be more vulnerable to security issues, and it can be expensive to make the call through JNI. If you decide to use JNI, you will lose one of Java’s advantages, being portable; all code that is implemented in a native language will not be portable and will have to be re-written to support other operating systems. For all these reasons, it is obvious you should first review your other options before investing the effort into developing a solution through the JNI. One way to get around using the JNI is through data communication, such as a TCP/IP connection. In this case, the programmer will need to set-up a client-server type system, and the server will need to determine how to interoperate the data. Using a TCP/IP connection does come with its own risks, but this is out of scope for this paper. Another possible solution is to use a JDBC (Java Database Connectivity) this solution simply connects Java to a database. The native application would need also to connect to the database, and the two programs would communicate information through the database. One risk you run with using a database is cost, if the database is not designed properly, maintaining the system may become expensive. Another alternative to using the JNI is by using distributed object technology such as the Java IDL API. An IDL (Interface Definition Language) can be used to find objects, call methods, and get returned data while on a remote system, using a TCP/IP connection.Although there are added complexities in using the Java Native Interface, there are some acceptable reasons for deciding to use it. All of the alternatives expect that the native part of the program and the Java part of the program can exist on separate processes, and sometimes on a different computer entirely. All of the cases in which it is acceptable to use the JNI, the native code and the Java code exist on the same process. Separating the program into processes may cause a “memory footprint” which may be problematic. It takes less memory to load a native library than to start a new process. Sometimes an application will need to use a feature that is not supported in the language; if such a case rises, the JNI may be the only solution. It is also accepted to use the JNI when you want to access an existing library from a native language. In the field of software engineering, you want to try to use already existing code; there is no sense in re-inventing the wheel. Code that has been rigorously tested should be used rather than risking creating new code that has not been tested on-site or in the field in which the code will be used. Java’s design concentrated on reliability, for example, Java ensures that the indices of an array are within range. Java’s concern of reliability did come with a tradeoff, the cost of execution time. Typically, when dealing with real-time embedded systems we have to deal with time-critical sections. Because Java typically cannot execute as fast as some native languages such as C, Java does not get considered as a possible solution for embedded systems or in any situations where time is the primary concern. With the JNI, Java could be used as a possible solution for embedded systems; we will examine this possibility later in this paper. Using the Java Native Interface will enable programmers to enjoy some of the advantages that other languages have to offer such as execution time. Java to C++ TutorialTo fully understand how the JNI works and how it could possibly be used, we must run through some tutorials. In this section, I will instruct you how to use the Java Native Interface to call a native function through Java. My tutorial was executed using the Windows 7 operating system, Java Development Kit (JDK) version 1.6_20 and a copy of Microsoft Visual Studio 2010. Although my tutorial was executed with Windows OS, JDK 1.6_20, and Visual Studio 2010, you do not need to do the same. You will need at least JDK 1.2 and some way to compile the C++ code into a DLL. This program will read in a file from myfile.txt that I do not provide, just make one. “myfile.txt” should include a list of numbers less than 10 digits with each number separated by one new line character feed. Comment out the call to “divideByZero”, this was used to demonstrate a crash. Add to your system variable path the location of your JDK bin folder. In my case, C:\Program Files (x86)\Java\jdk1.6.0_20\binWrite the Java class “jniBubbleSort” seen on the right side figure 1.1, save the file as “jniBubbleSort.java”Compile the Java code with javac jniBubbleSort.javaCreate the header file for C++ with javah jniBubbleSortWrite the native C++ code seen in figure 1.1, save the file as jniBubbleSort.cppExecute the vcvarsall.bat in the vc directory of Visual StudioCompile the native file into a dll using using cl -Iinclude -Iinclude\win32 -MD –LD jniBubbleSort.cpp -FejnijniBubbleSort.dll. The –Iinclude and –Iinclude\win32 is where the linker for jni.h and jni_md.h files exists respectively. The –MD links the jniBubbleSort.dll with the Win32 multithreaded C library.Now you should be able to execute the program using java jniBubbleSortFigure 2: Java to C++ using the JNIFrom the C++ code, you might be wondering what the extra parameters, JNIEnv * and the jobject are for. The JNIEnv pointer points to the list of JNI functions that are available for the C++ code to use. The JNIEnv can be used to access Java classes, methods, and fields that are a part of the Java Virtual Machine, but it is not used in this tutorial. In a later tutorial I will be using the JNIEnv to access a field in a Java class. For a list of JNI functions please visit . The jobject field is a reference to the Java object in which the native method belongs to. You can think of the jobject field passed as the reserved word ‘this’ in C++. If any parameters were intended to be passed, they would appear after the two parameters that are generated from the JNI. Earlier in this paper, “Determining to use the JNI”, it was stated that it is acceptable to use the JNI to use libraries previously written. In order to use previously used libraries, you will need to make a wrapper class due to the code generated by the JNI. To use a library previously written, create a Java class that will contain all the functions in the legacy library. For each native method created, you will then be able to call the native function you desire and return any data desired.C++ to Java: Embedding a JVM TutorialAs shown in figure 1.0, the JNI may also be used to embed a Java Virtual Machine within a native application. In order to embed a JVM into a native application, you must add to the system path environment variable to the file location of where the JVM.dll exists. In my case I found the jvm.dll file in the folder location “C:\Program Files (x86)\Java\jdk1.6.0_20\jre\bin\client”. Without the jvm.dll folder location in the environment variable path, you will be unable to initialize an instance of the Java Virtual Machine. To compile this program as an executable use cl -Iinclude -Iinclude\win32 -MD Sample2.cpp -FeRunMain.exe -link "C: \Program Files (x86)\Java\jdk1.6.0_20\lib\jvm.lib". In this tutorial created by IBM developerWorks, we will create a JVM and call a Java method to square the number 5. We will first create the JVM, and then find the class that contains the method to square our number, sample2.java. To find the Java class, we will use FindClass(“sample2”). After finding the Java class we will find the Method ID of the static method intMethod. To find the static method’s Method ID we use getStaticMethodID passing three parameters.Figure 3: C++ to Java using the JNI the Java class that was found using FindClassThe name of the method we are trying to find, intMethodLastly a string to represent the signature of the parameter that will be passed into the method and the return typeFor “intMethod”, there is only one parameter, an integer, the method also returns an integer, so the third parameter of “getStaticMethodID” looks like, “(I)I”. For a full list of signature types please visit this example, most of the code is for initializing the Java Virtual Machine. My tutorial has the C code and the Java code in separate directories, so I must set an option to point to the Sample2.class directory, -Djava.class.path=”c:/Users/Justin/Desktop/SeniorSem/cToJava/JavaCode”. Mapping TypesBecause Java and native languages such as C treat types differently, we must investigate how the Java Native Interface maps types. For example, Java treats arrays as objects and C treats arrays as pointers to memory locations. In the JNI, it treats types as either a primitive type or a reference type. Primitive types are defined as a specific number of bits and are either signed or unsigned. An example of a primitive type is a char. A character in Java is an unsigned 16-bit Unicode character. The Java character is mapped to an unsigned short in C called a jchar. In most cases, you can treat the jchar as a char in C, but note that the character set in Java can be twice as large as the ASCII table supported in C (8 bits). Reference types include everything that does not have a pre-defined bit size. The JNI splits reference types into a few different classes, each which inherit from jObject see figure 4. When using Java reference types, you cannot use the same syntax as you would if the type was native to C. For example, you cannot use myClass.doIt() to call the objects myClass’s doIt method. If you would like to call myClass’s doIt method you must find the class of the object and the method id to call the method using the JNI functions. In a later tutorial I will be showing this. There is also added syntax for accessing arrays. To access elements of a Java array, you must either copy over the elements of the array into a C array or retrieve a pointer to which the Java array exists. If the array size is small or you only want a small section of an array, it is typical to copy the elements into a C array with Get<Type>ArrayRegion, but if the array size is large it may get expensive to make a C copy of the array.Figure 1.2Within the different classes of references, they categorize these objects by how they can be removed from the virtual machine. There are local references, global references, and weak global references. Local references are objects that have been new’d by a native method and only exist during the execution of the native method. Local references are common because there is nothing that needs to be done to specify that the object is a local reference. If you want to make an object C static within a method, meaning there is only one reference that exists to the object within memory, you should not use a local reference. If a static object is declared local, it will not exist after the execution of the native method, and later calls to the native method could cause memory corruption or could cause the program to crash. To allow the C program to declare objects static, the programmer must use newGlobalRef or newWeakGlobalRef see figure 5 (code taken from #1) on the declarations. Global references exist until the programmer specifies to delete the object using DeleteGlobalRef. Weak references do not need to be specified to be deleted from memory, they can be garbage collected, but they may also be deleted by the programmer using deleteWeakGlobalRef.Figure 4: JNI objectsFigure 5: Code using a global referenceJNI IssuesAs stated in “Determining to use the JNI”, there are many issues that are related to using the JNI. In this section we will investigate the security problem, memory management issues, and check out the overhead cost of using the Java Native Interface. By using the JNI, it becomes very easy to read/write to data is Java you should not be able to. Because of the JNIEnv pointer, you have access to all classes and fields. So long as the native language knows about a class, it will be able to read/write to private fields and overwrite the values of constants. See figure 1.4 for code I wrote that exposes this issue and many other issues. This is not the only problem related to native languages accessing memory in Java’s heap. Typically for accessing large arrays that are a part of the Java’s heap because it can be expensive to make a copy of the array in the native language, you get a pointer to the array instead. With a pointer to the array, it is easy to go outside the bounds of the array and accidently read/overwrite data in Java’s heap not part of the array. When using the JNI, you will be responsible for memory management on the native language side. Although in C, this is nothing new, you will need to mange memory for the Java Virtual Machine. In my example, figure 6, I am not only accessing a private field, I have a memory leak. The memory leak exists because my call getStringUTFChars does not have a corresponding releaseStringUTFChars. Be very conscious about all JNI calls you make and ensure that you do not need to make a corresponding call to release memory. This example is also missing error checks. For every call from the native language to Java, you will need to check that errors have not occurred because the native language will not throw the exception. Figures 3 and 5 are using error checking for finding the classes and finding the method id’s. In figure 6, it probably should check that square has a value. Returning reference types from native languages can also cause problems. Let’s say you wanted to return an UnsafePerson seen in example 1.4 in a native function. The return type for this native function would be a jObject, so it would be really easy to return a different jObject than what the Java code was expecting.Figure 6: JNI coding issue exampleIn order to determine whether or not making use of the JNI for time critical situations, we investigate how much time it takes to make a call through the Java Native Interface. Demetruis L. Davis wrote benchmark tests for two algorithms, heap sort and Discrete Fast Fourier transform. The tests he ran were not optimized so these programs are not testing how good the optimization is for the compilers. In his research he runs his tests with different array sizes to check the impact of array sizes on Java and on C++. He also runs a number of iterations to determine how expensive it is to make the call through the JNI. The results are shown in three graphs on figure 7 separated by array size. This graph demonstrates, for this case it is worth making the call through the JNI to execute the C++ algorithm rather than executing the equivalent Java implementation. The far right graph on figure 7 shows a large performance hit for the JNI implementation, but this is not caused from the expense of the JNI. The author states that this graph most likely is due to memory limitations on the computer used for testing. For more statistical data test visit [4]. These metrics will be better at determining how much time it will cost for different JNI operations and on different Just-In-Time compilers. For most JNI operations, it takes a couple hundred nanoseconds, so typically it is fairly negligible.Figure 7: JNI execution time vs. pure Java execution timeJNI ApplicationsNow that we have investigated when we shouldl use the JNI and understand how some of the problems that can occur, we can begin to look into where it would be applicable to use the Java Native Interface in industry. C and assembly languages dominate the market for small embedded systems because of its efficiency and its ease to communicate with other languages. As embedded projects become larger and more complex, it becomes more difficult to maintain and to develop. Java has become more attractive for developing large embedded projects because there are a large number of developers that are familiar with the language and Java’s large library makes it easier to develop than C/C++. With the help of the JNI, Java could be used to write the “upper stream” [7] and have the C code control the devices. With Java being independent from the host platform, the Java software could be reused on different systems, reducing the cost of distributing the software on different machines.The Android phones follow this exact development scheme. Applications such as contacts, e-mail, browser settings, and all applications that come with the Android package are written in Java. Other programs that manage resources, such as voice applications are mostly written in Java. The Android does use C/C++ for CPU tasks and to drive other peripherals. Even Androids Virtual Machine, Dalvik Virtual Machine, is written in C.Design ConclusionsThis paper has shown you when it is acceptable to use the Java Native Interface. Once it has been decided to use the JNI, you must try to avoid some of the issues related. Designing a good system will also help prevent some of the issues that may happen such as memory leaks and keep the overhead of the JNI minimal. Try to keep the control flow simple, code that goes back and forth between the JVM and native code, is probably designed incorrectly. Every time you make calls back and forth you are wasting time. Keep the native code to a minimum, by doing this you will prevent memory leaks that could occur, and your code will be less complex. Separate your Java code from your native code. Native code is platform dependent, by separating the Java code from the native code; you will have a porting layer. A possible application for the JNI is for large embedded projects. Keep the microcontrollers written in C and have the “upper stream” such as applications within the embedded system written in Java. If you are interested in learning more about embedded development using the JNI, I would suggest starting with Android development.References[1] Sheng Liang (June 1999). The Java Native Interface Programmer’s Guide and Specification. Retrieved from [2] Gang Tan; Andrew W. Appel; Srimat Chakradhar; Anand Raghunathan; Srivaths Ravi; Daniel Wang (2006). Safe Java Native Interface. Retrieved from [3] Scott Stricker(March 2002). Java Programming with JNI. Retrieved from [4] Dawid Kurzyniec; Vaidy Sunderam. Efficient Cooperation between Java and NativeCodes - JNI Performance Benchmark. Retrieved from [5] Demetrius L. Davis. To JNI or not to JNI?Retrieved from [6] Preetham Chandrian (August 2011). Efficient Java Native Interface for Android based Mobile Devices.Retrieved from [7] Nguyen Thi Thu Trang; Tran Canh Toan; Nguyen Manh Tuan; Takenobu Aoshima (October 31, 2007). An experience in developing embedded software using JNI.Retrieved from js.vnu.edu.vn/tn_2_08/b7.pdf ................
................

In order to avoid copyright disputes, this page is only a partial summary.

Google Online Preview   Download