Interop mini-series – Calling C and C++ code from Java (Part 3)


In this post and the next, I will show how we can use the JNA (Java Native Access) library to perform the same interop operations as demonstrated in the first three parts of this mini-series (check them out!).

Contents

  1. The JNA library
    1. Installation
  2. Demo
    1. Building the C library
    2. Building the C++ library
    3. Demo run
  3. References

The JNA library

The JNA (Java Native Access) library is an alternative to using JNI in order to interop with C and C++ shared libraries. While JNI is a real pain to get right, JNA abstracts away all the gory details so that the developer can focus on the functionality instead of boilerplate.

Interestingly, JNA itself uses JNI stubs internally, connecting with the libffi library. (A link has been provided in the References section). This is why the library works across multiple platforms, and is still relatively lightweight.

Installation

JNA comprises of two Jar files – jna.jar and jna-platform.jar. jna.jar provides the core APIs while jna-platform contains the aforementioned stubs to interact with libffi.

For instance, the Mac OS X specific bits are secreted away in com.sun.jna.darwin/libjnidispatch.jnilib inside jna-platform.jar. Likewise for other platforms.

You can either download the JNA jars directly and use them, or use use Maven to integrate JNA functionality in your project. Again, I have provided all the links in the References section.

Demo

Top

For this demo, we will first see how the two native libraries (which our Java code will be using) are created, and then we will use them both in the same example.

The first library, libsysteminfo.dylib uses pure C code, whereas the other library, libnumbersorting.dylib uses C++ code with a C wrapper (the reason will be explained in the relevant section).

Building the C library

Top

For the C example, I decided to use use the native library to get some useful system information – architecture type, model name, memory, number of cpus, and the number of logical cpus (cores).

Note that this example works only in Mac OS X. For Linux, the sysctlbyname function can be replaced by sysctl with appropriate changes. For Windows, you will have to check which kernel call provides the same functionality.

We will use the sysctlbyname function to extract these values.

Let’s define the header file first (in system_info.h):

#ifndef __SYSTEM_INFO_H__
#define __SYSTEM_INFO_H__ "system_info.h"

#include <sys/types.h>
#include <sys/sysctl.h>

#ifdef __cplusplus
extern "C" {
#endif

char* get_machine();
char* get_model();
int64_t get_memory();
int32_t get_ncpu();
int32_t get_nlogicalcpu();

#ifdef __cplusplus
}
#endif
#endif

And the corresponding C implementation (in system_info.c:

#include <stdio.h>
#include "system_info.h"

#define MAXSIZE 210

char* get_machine()
{
    static char machine[MAXSIZE];
    size_t len = sizeof(machine);

    sysctlbyname("hw.machine", &machine, &len, NULL, 0);

    return machine;
}

char* get_model()
{
    static char model[MAXSIZE];
    size_t len = sizeof(model);

    sysctlbyname("hw.model", &model, &len, NULL, 0);

    return model;
}

int64_t get_memory()
{
    int64_t mem;
    size_t len = sizeof(mem);

    sysctlbyname("hw.memsize", &mem, &len, NULL, 0);

    return mem;
}

int32_t get_ncpu()
{
    int32_t cpu;
    size_t len = sizeof(cpu);

    sysctlbyname("hw.ncpu", &cpu, &len, NULL, 0);

    return cpu;
}


int32_t get_nlogicalcpu()
{
    int32_t logical_cpu;
    size_t len = sizeof(logical_cpu);

    sysctlbyname("hw.logicalcpu", &logical_cpu, &len, NULL, 0);

    return logical_cpu;
}

int main(int argc, char* argv[])
{
    printf("%s, %s, %lld, %d, %d\n", 
            get_machine(),
            get_model(),
            get_memory(),
            get_ncpu(),
            get_nlogicalcpu());

    return 0;
}

Let’s compile it into a shared library (in this case, Clang + LLVM on Mac OS X. For other compilers such as gcc proper, check the relevant documentation):

Timmys-MacBook-Pro:c_demo_system_info z0ltan$ clang -dynamiclib -o libsysteminfo.lib system_info.c

Timmys-MacBook-Pro:c_demo_system_info z0ltan$ ls
libsysteminfo.lib	system_info.c		system_info.h

Building the C++ library

Top

This is the more interesting bit for more than one reason! In this example, let’s try and sort an array of integers using the native library.

Again, let’s write the interface out first (in number_sorting.h):

#ifndef __NUMBER_SORTING_H__ 
#define __NUMBER_SORTING_H__ "number_sorting.h"

void callback_function(int[], int);

extern "C" {
    void sort_numbers(int[], int);
}
#endif

Looks good, but what’s the deal with the callback_function? We’ll get to that in just a monent. For now, let’s flesh out the functionality for this interface (in number_sorting.cpp:

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

void sort_vector(std::vector<int>&, int[], int);

void callback_function(int array[], int size)
{
    std::vector<int> vec(size);

    sort_vector(vec, array, size);

    int i = 0;
    for (std::vector<int>::const_iterator it = vec.begin(); it != vec.end(); it++)
        array[i++] = *it;
}
    

template <typename T>
void display_elements(const std::vector<T>& vec)
{
    for (std::vector<int>::const_iterator it = vec.begin(); it != vec.end(); it++)
        std::cout << *it << " ";
    std::cout << std::endl;
}


void sort_vector(std::vector<int>& v, int numbers[], int count)
{
    for (int i = 0; i < count; i++)
        v[i] = numbers[i];

    display_elements(v);

    std::sort(v.begin(), v.end(), [](int x, int y) { return x < y; });
}

int main()
{
    std::ios_base::sync_with_stdio(false);

    int sample[] = { 1, 2, 0, -1, 3, 199, 200, 110, -234, 12345 };

    callback_function(sample, sizeof(sample)/sizeof(sample[0]));

    for (int i = 0; i < (int)sizeof(sample)/sizeof(sample[0]); i++)
        std::cout << sample[i] << " ";
    std::cout << std::endl;

    return 0;
}

Hmmm, this seems a bit too convoluted for this simple example? Why all the indirection? The reason will become crystal clear once we define the corresponding C file (in number_sorting.c) as well:

#include "number_sorting.h"

void sort_numbers(int numbers[], int n)
{
    callback_function(numbers, n);
}

Explanation: The reasons why we need both number_sorting.c and number_sorting.cpp, both of which implement the same interface, number_sorting.h
are two-fold:

  1. Since we are using some C++-only features such std::vector , std::sort, and C++11 lambdas, we need to invoke them in a separate function
  2. And the more important reason – C++’s pernicious name-mangling

Now, if we had simply written the entire sorting functionality using integer arrays and sorted using with C-like constructs (say, qsort, or a manually written sorting function), we wouldn’t need all this indirection, and we could have simply written the header as:

#ifndef __NUMBER_SORTING_H__ 
#define __NUMBER_SORTING_H__ "number_sorting.h"

extern "C" {
    void sort_numbers(int[], int);
}
#endif

and provided the implementation in number_sorting.cpp alone. That would have worked out fine. However, because we use all those C++ template constructs as well as functional constructs, if we had used this same header file, we would have got a name-mangling issue, and the function would not be visible to the Java client! (note: there are tools such as JNAerator which can help resolve name mangling issues, but that is beyond the scope of this tutorial).

To get around this, we write a C wrapper (number_sorting.c) which simply invokes the C++ function callback_function defined in number_sorting.cpp. Now you may think that we could have simply embedded callback_function inside the definition of sort_numbers in the C++ file alone, but that would not work either. Check out the reference “How to mix C and C++” in the “References section” for more details.

All right, let’s compile the code and generate the shared library:

Timmys-MacBook-Pro:c++_demo_sorting z0ltan$ clang++ -std=c++11 -stdlib=libc++ -dynamiclib -o libnumbersorting.dylib number_sorting.c number_sorting.cpp
clang: warning: treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated

Timmys-MacBook-Pro:c++_demo_sorting z0ltan$ ls
libnumbersorting.dylib	number_sorting.c	number_sorting.cpp	number_sorting.h

Timmys-MacBook-Pro:c++_demo_sorting z0ltan$ nm -gU libnumbersorting.dylib 
0000000000000a10 T __Z11sort_vectorRNSt3__16vectorIiNS_9allocatorIiEEEEPii
0000000000000730 T __Z17callback_functionPii
0000000000000c00 T _main
0000000000000700 T _sort_numbers

We can also see that the function sort_numbers has not been subjected to name-mangling.

Demo Run

Top

The Java code to plug into the native libraries is surprisingly concise and simple (in JavaToC.java):

import com.sun.jna.Native;
import com.sun.jna.Platform;
import com.sun.jna.Library;

public class JavaToC {
    public interface SystemInfoLib extends Library {
        SystemInfoLib INSTANCE = (SystemInfoLib)Native.loadLibrary("libsysteminfo.dylib",
                                    SystemInfoLib.class);

        String get_machine();
        String get_model();
        long get_memory();
        int get_ncpu();
        int get_nlogicalcpu();
    }

    public static native void sort_numbers(int[] numbers, int count);

    static {
        Native.register("libnumbersorting.dylib");
    }

    public static void main(String[] args) {
        System.out.println("System information:");
        System.out.format("Arch: %s, Model: %s, Memory: %dGB, CPUs: %d, Logical CPUs: %d\n\n",
            SystemInfoLib.INSTANCE.get_machine(),
            SystemInfoLib.INSTANCE.get_model(),
            SystemInfoLib.INSTANCE.get_memory()/ (1024*1024*1024),
            SystemInfoLib.INSTANCE.get_ncpu(),
            SystemInfoLib.INSTANCE.get_nlogicalcpu());

        int[] numbers = new int[]{10, 25, -100, 199, 0, 1, 1, 98, 99, 100};
        
        sort_numbers(numbers, numbers.length);
           
        for (int n : numbers) {
            System.out.format("%d ", n);
        }
        System.out.println();
    }
}

And the output:

Timmys-MacBook-Pro:Java-to-C z0ltan$ javac -cp "./:./jna.jar" JavaToC.java

Timmys-MacBook-Pro:Java-to-C z0ltan$ java -cp "./:./jna.jar" JavaToC
System information:
Arch: x86_64, Model: MacBookPro11,2, Memory: 16GB, CPUs: 8, Logical CPUs: 8

10 25 -100 199 0 1 1 98 99 100 
-100 0 1 1 10 25 98 99 100 199 

Perfect! Now let’s do a brief rundown on the Java code.

Explanation: There are basically two main ways in which to use JNA to load the native library:

    1. Implement an interface that extends the Library interface, use Native.loadLibrary() to load the specific library for your platform. The JNA class Platform provides some helper methods and static fields for this purpose.
      You can then include the declarations for the native methods inside your interface. Note that the names of the methods must exactly match those in the native library.

 

  1. The other way (preferred if you are only interested in a few functions) is to load the native library statically using a static block and the Native.register method.
    Along with this, you also need to declare the functions of interest as static native methods. Take care to ensure that the names again match with those defined inside the native library.

In the given example, both approaches have been amply demonstrated – libsysteminfo.dylib is loaded using the first approach since there are quite a few functions that I am interested in using.

On the other hand, since there is only a single function the libnumbersorting.dylib native library, it’s more convenient to load it a static native method instead.

Pay special attention to the data types used in the declarations of the methods. Just like with cffi, the data types match quite nicely with the native types in most cases. Refer to the documentation for more specific type declarations.

In the next post, we will see how to write callback functions in Java that can be invoked by functions from inside native libraries.

References

Top

Some useful links for the JNA library:

Advertisements
Interop mini-series – Calling C and C++ code from Java (Part 3)

Speak your mind!

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s