Comments
yourfanat wrote: I am using another tool for Oracle developers - dbForge Studio for Oracle. This IDE has lots of usefull features, among them: oracle designer, code competion and formatter, query builder, debugger, profiler, erxport/import, reports and many others. The latest version supports Oracle 12C. More information here.
Cloud Expo on Google News
SYS-CON.TV
Cloud Expo & Virtualization 2009 East
PLATINUM SPONSORS:
IBM
Smarter Business Solutions Through Dynamic Infrastructure
IBM
Smarter Insights: How the CIO Becomes a Hero Again
Microsoft
Windows Azure
GOLD SPONSORS:
Appsense
Why VDI?
CA
Maximizing the Business Value of Virtualization in Enterprise and Cloud Computing Environments
ExactTarget
Messaging in the Cloud - Email, SMS and Voice
Freedom OSS
Stairway to the Cloud
Sun
Sun's Incubation Platform: Helping Startups Serve the Enterprise
POWER PANELS:
Cloud Computing & Enterprise IT: Cost & Operational Benefits
How and Why is a Flexible IT Infrastructure the Key To the Future?
Click For 2008 West
Event Webcasts
Using the Java Native Interface Productively
Using the Java Native Interface Productively

Although we try to make our applications pure Java, outside forces sometimes make this impossible. We had such a case recently in our shop when we had to interface to an external device with an API that supported C language calls.

This is a typical case for the Java Native Interface (JNI). The JNI provides Java programs with a gateway to other languages and enables applications written in other languages to invoke the Java Virtual Machine.

This article focuses on the first of these two uses. Specifically, it discusses supporting a C/C++ API in Java to allow a Java application to use it. This is probably the situation in which the JNI is most frequently employed in production environments. Indeed, the seminal work on the JNI, The Java Native Interface: Programmer's Guide and Specification by Sheng Liang, devotes a chapter to this question. There's no theory involved, but if you use the following techniques you should save yourself many hours of work.

Phase one of the JNI programmer's acclimatization process: dump the sacred principle of Java development - platform independence. It must, as a purely logical implication, go out the window. All the C/C++ code in this article compiles under GNU C/C++ on Solaris and Microsoft Visual C++ on the Win32 platform. Other platforms would have to be tested individually.

I'll focus on the practical problem of implementing a large and complex API using the JNI within a production environment. Space constraints required Liang to discuss passing parameters that consisted of Java primitives from Java to C/C++ (hereafter C++, unless the distinction matters) and sending the C++ return code to the Java application. However, most APIs are more complex. This is something not frequently addressed in the literature, despite its prevalence in the real world. APIs are not restricted to primitives as parameters but pass structures and classes to the device. They not only use return statements but also assign values to the passed parameters for use by the calling program. They present questions for interpreting Java classes in C++, handling structure alignment and data format issues, and implementing code that is source compatible with both 32-bit and 64-bit processors.

The JNI, Power - at a Price
Every developer who has used the JNI is aware that it's powerful and comprehensive. However, the price of these features is an inordinate amount of work to accomplish some very mundane tasks. In particular, parameter handling is a time- and code-intensive business that's subject to errors and latent bugs. Consider the code in Listing 1, which might be described as the simplest function to access a JNI. The native function is declared at (1) in the listing. (In Listings 1, 4, and 6 I've numbered specific lines for reference purposes.)

The method passes one integer to a C++ native function. The native function prints the passed parameter to stdout and also returns it. The native code is shown in Listing 2. Note the obscure function name, which is generated by javah.

The native function just prints the integer parameter to stdout. How this appears depends on your Java development environment. In Borland JBuilder the output from the native code and the System.out.println()call both appear in the IDE message window, as shown below.

Value is 5
Value sent and returned is 5

The native code prints the first line and Java prints the second. The important points about this example are:

  • The integer parameter is passed from Java to C++ as a primitive, so the native code can access it directly.
  • The same rule applies to all other Java primitives.
  • The use of the JNI is straightforward, and the native code can be developed rapidly as long as its use is confined to passing primitives.
  • The native code may require modification for different platforms. While ISO standardization has made C++ source code fairly portable, we're out of the area of Java binary compatibility.

    Most data types are more complicated than primitives. In Listings 3 and 4 the Java and native code for passing and returning a string type are shown. The following is the output from these listings.

    Value is Hello World
    Value returned is Goodbye World

    While there's virtually no change in the Java code (essentially just a change of the operative variable type from int to string), the native code is substantially more involved. Because a string is neither a primitive type nor architecturally the same as any string in the native language, it must be handled differently. First, the JNI function GetStringUTFChars() [at (1) in Listing 4] both converts the string to the C++ single-byte character set and retrieves a pointer to the resultant object (other JNI functions can handle conversion to C++ wide, e.g., Unicode, strings). Second, the JNI function ReleaseStringUTF() frees the memory allocated for the converted string. Third, the JNI function NewStringUTF() allocates a Java string from a C++ single-byte character string to return a string to Java. The important point about Listing 3 is that passing strings increase the amount of native code necessary to process the parameter.

    String parameters don't present even the normal level of variable complexity. Real-world data types are likely to be represented as classes in Java and structures or classes in C++. Listings 5 and 6 provide the Java and C++ code for passing a simple class.

    The Java code in Listing 5 is very simple. It just instantiates instances of ClassAccess and Device2 where Device2 aggregates an instance of Device1, which it instantiates in the constructor chain. The code is straightforward, indeed mechanical. However, the native method (see Listing 6) is completely different. While not conceptually sophisticated, it's a complicated tangle of repetitive operations on different data types. First, at (1), the code fetches the class of the object passed to the native function. This is the Device2 object passed in the Java line classAccess.accessClass(dev2). It then proceeds to retrieve each parameter in turn. The block at (2) retrieves the int field intValue. The code at (3) retrieves the long field longValue. The block at (4) is more involved as it retrieves the string field stringValue and resembles Listing 3. Finally, the block that begins at (5) shows the more involved case of obtaining a field from an object contained within another object. In this example the Device2 object contains a Device1 object. It's the int member of Device1, intValue, that we wish to eventually obtain. [This is obtained at (6).]

    Simplifying the Implementation
    Code like this will be repeated unless we find a way to move its regularities into separate functions. The string and contained object cases generate the most overhead, so they're the ones chosen in Listing 7. Now the native code in Listing 5 can be abbreviated to that shown in Listing 8.

    Furthermore, the two access functions repeat the simplification that they provide here in each native function they're called in. In each instance that we use this code we reduce the amount of code required to be implemented in the project by roughly two-thirds.

    Assigning Data to Java Structures
    Now we're at the point at which productive use of the JNI became a major issue in our shop. The vendor API that we supported required some structure passing and what we discussed earlier proved useful for this. However, the API's main purpose was to return data from the vendor's device, which it did by providing over 100 functions that required a pointer to an API structure to be passed to an API function. The vendor library allocated memory for the structure and instantiated the fields with the requested data. This worked well for the C++ programmer who could examine the data by dereferencing the pointer after the API function call returned.

    The Java programmer acquired a new set of problems. We decided to define a Java class for each C++ structure (a practice recommended by Bloch). Each field of each API structure had to be assigned to a member of the corresponding Java class, passed as a parameter. The one-to-one mapping of Java classes to C++ structures gave us over 60 Java classes. The number of structure members meant that over 500 member assignments had to be made. Code resembling that in Listings 1-5 would have to be written and, more important, maintained - a daunting prospect.

    We turned to a set of functions like those in Listing 7. However, now these functions would read a member variable from a C++ structure and assign the value to the Java class (see Listing 9 for an example). For continuity, we continue to use the Java classes Device1 and Device2 defined earlier. Their C++ equivalents are:

    struct Device1
    {
    int intValue;
    };


    struct Device3
    {
    int intValue;
    long longValue;
    char * stringValue;
    Device1 dev1Value;
    };

    Assume that at runtime they contain the values shown below:

    struct Device1 dev1 = {10};

    struct Device3 dev3 = {
    5,
    8,
    "Hello World",
    {10}
    };

    Listing 9 is the Java program that will print out these values.

    The C++ code (see Listing 10) shows the implementation of the native method assignClass. One interesting feature is that it shows how to make assignments to nested objects (Device1 is aggregated by Device3 in Listing 9). Just as in the case of passing parameters from Java to C++ we provide a set of routines to expedite the process and make the code more reliable (see Listing 10). Listing 11 can then be replaced with Listing 12. (Listings 11 and 12 can be downloaded from the JDJ Web site, www.sys-con.com/java/sourcec.cfm.)

    Benefits
    The family of Parse... functions has substantially reduced the amount of code required to make the necessary assignments to the Java object passed as jOutput and its contained objects. Furthermore, we've moved the intricate code in which errors can occur into a set of reusable functions. This expedites the implementation of a large vendor API in Java and makes maintenance more straightforward. These functions are extensible to other variable types as necessary. We've implemented only the ones that we need.

    Summary
    The JNI is a powerful, comprehensive solution to the problem of accessing an API written to support other languages, especially C or C++. That power comes at the price of intricate parameter manipulations. In this article we addressed the problem of using the JNI in a production situation and presented a set of techniques that simplify most of the repetitive tasks of passing information to the native code and getting it back into our Java program. These techniques reduced by around two-thirds the amount of C++ code that had to be written. These techniques can be extended to cover other constructs, and it's a pragmatic choice as to which should be implemented in a particular project.

    Postscript
    After completing the project in which these techniques were developed, another JNI project came up. Implementation of the native parts of the project took about a quarter of the time that we had forecast, based on raw coding of JNI function calls. The code also passed all unit tests on Solaris and Windows the first time, an indication of the reliability gains of a unified method of handling parameter passing. While an unscientific estimate (different APIs, etc.), the difference in terms of schedule improvements was very noticeable.

    References

  • Liang, S. (1999). The Java Native Interface: Programmer's Guide and Specification. Addison-Wesley.
  • Bloch, J. (2001). Effective Java Programming Language Guide. Addison-Wesley.
    About Andrew Chalk
    The author is President of Magna Carta Software, Inc. in Plano, TX USA. He can be contacted at achalk@magnacartasoftware.com

  • In order to post a comment you need to be registered and logged in.

    Register | Sign-in

    Reader Feedback: Page 1 of 1

    There are products collectively called JNI Proxies that let you use C APIs in Java without writing a line of JNI code - see "Proxy Products" under the link.

    Of those listed, xFunction is also available for Linux.


    Your Feedback
    Dmitry Leskov wrote: There are products collectively called JNI Proxies that let you use C APIs in Java without writing a line of JNI code - see "Proxy Products" under the link. Of those listed, xFunction is also available for Linux.
    Latest Cloud Developer Stories
    Sanjeev Sharma Joins November 11-13, 2018 @DevOpsSummit at @CloudEXPO New York Faculty. Sanjeev Sharma is an internationally known DevOps and Cloud Transformation thought leader, technology executive, and author. Sanjeev's industry experience includes tenures as CTO, Technical Sa...
    DXWorldEXPO LLC announced today that Kevin Jackson joined the faculty of CloudEXPO's "10-Year Anniversary Event" which will take place on November 11-13, 2018 in New York City. Kevin L. Jackson is a globally recognized cloud computing expert and Founder/Author of the award win...
    When applications are hosted on servers, they produce immense quantities of logging data. Quality engineers should verify that apps are producing log data that is existent, correct, consumable, and complete. Otherwise, apps in production are not easily monitored, have issues that...
    The best way to leverage your Cloud Expo presence as a sponsor and exhibitor is to plan your news announcements around our events. The press covering Cloud Expo and @ThingsExpo will have access to these releases and will amplify your news announcements. More than two dozen Cloud ...
    When building large, cloud-based applications that operate at a high scale, it's important to maintain a high availability and resilience to failures. In order to do that, you must be tolerant of failures, even in light of failures in other areas of your application. "Fly two mis...
    Subscribe to the World's Most Powerful Newsletters
    Subscribe to Our Rss Feeds & Get Your SYS-CON News Live!
    Click to Add our RSS Feeds to the Service of Your Choice:
    Google Reader or Homepage Add to My Yahoo! Subscribe with Bloglines Subscribe in NewsGator Online
    myFeedster Add to My AOL Subscribe in Rojo Add 'Hugg' to Newsburst from CNET News.com Kinja Digest View Additional SYS-CON Feeds
    Publish Your Article! Please send it to editorial(at)sys-con.com!

    Advertise on this site! Contact advertising(at)sys-con.com! 201 802-3021



    SYS-CON Featured Whitepapers
    ADS BY GOOGLE