Template-Generated JNI

C/C++ Users Journal August, 2004

Converting Java data types to/from C++ data types

By Vladislav Tcheprasov

Vladislav Tcheprasov holds degrees from Moscow State Technical University and Michigan State University. He currently is lead software engineer for FairIsaac Corp. He can be contacted at vladislav@sbcglobal.net.

The Java Native Interface (JNI) was designed to facilitate cross-platform development between Java and languages such as C++. However, writing JNI code can be tedious. With that in mind, the template-based approach I present here eliminates much of the tedium by avoiding JNI coding for newly created wrapper functions. The complete source code for the framework I present is available at http://www.cuj.com/code/.

JNI usually provides conversion of Java data types to/from C++ data types by transforming arguments passed from Java to the appropriate C++ types, then converting results of the C++ functions (including those passed by reference) to Java types. In this fashion, function calls to the actual C++ libraries can resume with the appropriate arguments and the Java side can receive results meaningful to its environment. In middleware terms, this process is called "data marshaling."

My first approach was to combine template-based Boost function holders with the JNI helpers from [1]. However, after several attempts, I came up with a lighter design using techniques derived from the variant described in "An Improved Variant Type Based on Member Templates" [2]. The main idea was to create two types of variants—one that "preserves" the function signature and another that provides data marshaling according to this signature for every argument passed in from Java.

To make the argument-passing mechanism uniform, it was simpler to restrict them to the Java Object type. This lets Java arguments be delivered to the JNI layer in a single Java Object[] array. At the same time, the Java Object's reflection mechanism lets the templates determine the "convertibility" of the Java types passed to the C++ side at runtime. For example, string can be converted to const char*, or Java's long[] array can be marshaled to vector<int>.

For this type of data marshaling, I introduced the class jobj_wrapper with the template-based type cast operator (template <typename T> operator T () const). The jobj_wrapper class uses the variant (modified version from [2]) to dynamically create and hold the appropriate C++ data object. Data within a jobj_wrapper variant object are created from the Java Object in accordance with the invoked casting operator. If such a conversion is not meaningful (for example, String to int*) the casting operator throws an invalid_argument exception. Most of the templates and their specializations within the jobj_wrapper class are intended to separate types of different nature, and eventually create C++ data objects that may hold data required by a function's signature.

To satisfy the template specialization in the Visual C 7.0 compiler, I had to add some tricks such as removing references (see class remove_ref), which are probably not needed for compilers with better C++ standard conformance. First, two specializations separate (nonreference) vector types from others:

template <typename T> 
  T vector_vs_not(const T& ) const;
template <typename T> vector<T>& 
    vector_vs_not(const 
  vector<T>& ) const;

The second specialization sifts out everything that is not of type std::vector. Pointer types to primitives converted from Java arrays use C-style arrays (for example, Java's int[] can become long* on the C/C++ side).

Following the specialization on the std::vector branch, the vector_specific_cast member function exploits arr_specific_return to construct and initialize the appropriate vector type from the Java array. The arr_specific_return function's primary role is to prepare the corresponding vector. Listing 1 is an example of testing one of the possible conversions from Java's array of integers to the vector of the "yet to be defined" C type.

I introduce an additional layer of data initialization through the function vector_init to allow only Java long[] and int[] arrays to be converted to a vector or C-style array containing pointers. It may be safer to limit it to Java long[], depending on the size of the pointers on a particular development platform. Fortunately, Java's arrays with primitives are of the Object type and can be explored at runtime. Currently, implemented conversions between arrays of primitive types are very allowing. A Java array of any primitive can be transformed to a vector of any primitive type. An additional layer of specializations can be inserted to prevent transformations with possible data loss. Other branches of the template-based casts can be reviewed in the accompanying source code.

Within the function-oriented variant (func_variant) there are three types of classes that accept and implement different types of function signatures. Listing 2 shows examples of class templates for functions with one argument: Impl<R,A0>, for the regular function; MemFuncImpl_1<R,C,A0>, for the member function; and ConstMemFuncImpl_1<R,C,A0>, for the constant member function. (This explains why the casting operator is implemented as a constant with a mutable data holder.) R stands for the return type, C for the class, and A# for the type of argument. Currently, the provided header describes all three function types with up to four arguments, but can be respectively modified if there is a need for more arguments. Listing 2 provides implementation details of the constant member function variant (ConstMemFuncImpl_1). This class contains returnResult with an actual call to the C++ function. The pointer to the C++ object is converted from the first Java argument, and the second argument becomes the first argument passed to the C++ function. The necessity of the returnResult functions and functors specializations with void arose from dealing with functions that do not return any values. Functions that do return the result call return_jobj_from_native_type to the convert returned C++ objects to the appropriate Java Objects.

At this point, you can provide one entry point to the JNI layer passing the function name as a String and the array of Objects as arguments. To find a particular function by its name, I provided a map that holds function variants. Listing 3 describes the implementation of the map, its construction, and the function get_functor that uses the map to return the corresponding function variant.

The macro REGISTER_FUNCTOR_TO_MAP creates pairs of function name variants. Using a comma operator, these pairs are counted within a definition of a static array and then the static map gets initialized (one reason to provide the variant's copy constructor). The last thing to do is to provide the JNI call itself. The single exported library function Java_jni_util_invoke implements this. It handles checking and/or (re)setting the Java environment variables on the C++ side, initializes the vector of type variants from the array of Object arguments, gets the function variant from the map by name (String parameter), and eventually, invokes this function variant and resets the Java array arguments. There are two ways to get results from the C/C++ functions. The returned results get converted into the proper Java Object that can represent it on the Java side. If referenced arguments get modified, then the new Java Objects are updated or created according to their types and replaced within the array of arguments. Thus, they can be retrieved from this array and processed on the Java side.

The helper Java class GenericCall2CLib has a native function (invoke) that calls into the C++ library. It is static and public, and therefore, can be directly exploited by other packages. Another way of calling the native function is to create an object of this class initialized by the C++ function name, set its arguments, and invoke it (Listing 3). Currently, the library linkage happens when the GenericCall2CLib class gets loaded. A more generic loading mechanism can be easily added if needed. The accompanying test code has the Visual C 7.0 project and files to review and try this framework. Created C++ object code can be either directly linked to an existing library or be built as a standalone shared library that dynamically links to the libraries whose functions it wraps.

Conclusion

The framework I present here is very lightweight—only two C/C++ files and one Java file. It removes your manual JNI coding in most circumstances (the only C coding you may need to provide involves registering functions). Thus, developers on either side can concentrate more on problem-oriented tasks. In comparison to a similar framework provided by Sun, it has a runtime type conversion and checking; it is type-safe as long as you test passing right (or convertible) arguments from the Java side. Moreover, it gives access to the C++ functions and methods while breaking dependencies on a particular compiler-mangling scheme (it provides a single C-style entry point to a bridge library). The current framework is tailored to work with STL or regular C types, but is flexible and can be modified to handle platform-specific types as a CString. It has proved to be robust and handy with medium-sized applications.

Acknowledgments

Thanks to my work at FairIsaac, which made me think of fast integration of the Java front end and C++ libraries. Special thanks to Jason Miyasato who, despite his tight schedule, found time to edit my paper. Thanks to the contributing authors of CUJ articles that gave me insight into an elegant solution to this problem.

References

  1. [1] Finkelstein, Lev and Evgeniy Gabrilovich. "JNI-C++ Integration Made Easy," CUJ, January 2001.
  2. [2] Cacciola, Fernando. "An Improved Variant Type Based on Member Templates," CUJ, October 2000.