Wednesday, September 30, 2015

How to reference C++ lib in Java via JNI in OS X / Linux Ubuntu and Command Line / Eclipse

In my previous post, I was talking about the reference C++ dll in Windows (http://chianingwang.blogspot.com/2014/09/how-to-reference-c-dll-vs-2013-from.html). This post, I will share how to reference C++ lib in OS X. 

Again, the logic is pretty straightforward but the exception you might run into might take time to resolve it. Here is what's I try and I highlight them in yellow.


outline

  • Creating the Java Class for JNI shared ( function / arguments )
  • Compiling the Java Class for Generating C++ header 
  • Composing the C++ cpp or cc class
  • Sorting out the "jni.h" location as g++ reference
  • Compiling the C++ code for dynamic c lib
  • Run Java to call C++ Class
  • Using Eclipse in Ubuntu
    • C++ Shared Library Project
    • Java Project to run *.so


Creating the Java Class for JNI shared ( function / arguments )


We start by creating a Hello World java class with the C++ lib Class name we would like to have and method(function) we would like to use in C++ code.



$ vi HelloWorld.java

$ cat HelloWorld.java

class HelloWorld {

    private native void print();

    public static void main(String[] args) {

        new HelloWorld().print();

    }

    static {

        System.loadLibrary("HelloWorld");

    } 

}

This static section get executed first, which expects to load a JNI shared or dynamic library known as "HelloWorld".

PS: HelloWorld is C++ lib name, HelloWorld is C++ class name and print is the method(function) name.

Compiling the Java Class for Generating C++ header 

We need to compile the java class involves opening up the terminal and issuing the command as below, which is using javac to generate the class file that is needed to generate the appropriate JNI C++ classes.

$ javac HelloWorld.java

Creating the JNI C++ header, we can run the cli as below. I cat the C++ header content as below as well.


$ javah -jni HelloWorld

$ cat HelloWorld.h

/* DO NOT EDIT THIS FILE - it is machine generated */

#include <jni.h>

/* Header for class HelloWorld */



#ifndef _Included_HelloWorld

#define _Included_HelloWorld

#ifdef __cplusplus

extern "C" {

#endif

/*

 * Class:     HelloWorld

 * Method:    print

 * Signature: ()V

 */
JNIEXPORT void JNICALL Java_HelloWorld_print
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

Composing the C++ cpp or cc class

C++ header is more like a window or declaration for your class and method(function) which let java know how to connect with C++ lib. After we got the C++ *.h, we need to compile C++ native code into dynamic library. (In OS X, jni shared/dymaic libraries have the extension .jnilib).


$ vi HelloWorld.cpp

$ cat HelloWorld.cpp

#include <jni.h>

#include <iostream>

#include "HelloWorld.h"

using namespace std;



JNIEXPORT void JNICALL

Java_HelloWorld_print(JNIEnv *, jobject){

    cout << "Oh Johnny, how handsome you are!\n";

    return;

}

Sorting out the "jni.h" location as g++ reference

In OS X, using finder to type jni.h and find the location for right click the file you found and display the info, then you will see the location for jni.h.




Compiling the C++ code for dynamic c lib

Generating Object File

To include the native code in the compilation of the java program, we need to compile native C++ code into dynamic library (OS X, jni shared/dynamic libraries have the extension .jnilib.) This is big different from the extensions on Windows and Solaris machine:, dll and so respectively.

We will need to leverage the jni.h to compile the C++ code into C++ *.o which is object file. Here, we include the -c option to generate the object file: HelloWorld.o from C++ code: HelloWorld.cpp, plus including jni.h path.



$ ls -lart

-rw-r--r--   1 johnnywa  staff    200 Sep 29 22:52 HelloWorld.java

-rw-r--r--   1 johnnywa  staff    377 Sep 29 22:53 HelloWorld.h

-rw-r--r--   1 johnnywa  staff    214 Sep 29 22:55 HelloWorld.cpp

-rw-r--r--   1 johnnywa  staff    442 Sep 30 11:57 HelloWorld.class

drwxr-xr-x  12 johnnywa  staff    408 Sep 30 12:47 .

$ g++ "-I/System/Library/Frameworks/JavaVM.framework/Versions/A/Headers" -c HelloWorld.cpp

$ ls -lart

-rw-r--r--   1 johnnywa  staff    200 Sep 29 22:52 HelloWorld.java

-rw-r--r--   1 johnnywa  staff    377 Sep 29 22:53 HelloWorld.h

-rw-r--r--   1 johnnywa  staff    214 Sep 29 22:55 HelloWorld.cpp

-rw-r--r--   1 johnnywa  staff    442 Sep 30 11:57 HelloWorld.class

-rw-r--r--   1 johnnywa  staff   6504 Sep 30 12:47 HelloWorld.o

drwxr-xr-x  13 johnnywa  staff    442 Sep 30 12:47 .



Generating dynamiclib

Finally, we need to use the -dynamiclib option to specify that the compiler shouldn't produce a standard executable, but should produce a library. The -o option is used to name the library with the appropriate extension: libhelloworld.jnilib. We also include the object file we just generate in previous step.

$ g++ -dynamiclib -o libhelloworld.jnilib HelloWorld.o
$ ls -lart
-rw-r--r--   1 johnnywa  staff    200 Sep 29 22:52 HelloWorld.java
-rw-r--r--   1 johnnywa  staff    377 Sep 29 22:53 HelloWorld.h
-rw-r--r--   1 johnnywa  staff    214 Sep 29 22:55 HelloWorld.cpp
-rw-r--r--   1 johnnywa  staff    442 Sep 30 11:57 HelloWorld.class
-rw-r--r--   1 johnnywa  staff   6504 Sep 30 12:47 HelloWorld.o
-rwxr-xr-x   1 johnnywa  staff  14764 Sep 30 12:51 libhelloworld.jnilib
drwxr-xr-x  14 johnnywa  staff    476 Sep 30 12:51 .

Run Java to call C++ Class



Since the java file and libhelloworld.jnilib is sitting in the same directory

$ java HelloWorld

Oh Johnny, how handsome you are!




Using Eclipse to in Linux ( Ubuntu )



C++ Shared Library Project for C++ object

Using in Eclipse is the same just create a C++ project but using "Shared Library" and Choose "Linux C++", the copy HelloWorld.h under src.





















and Create a new Class file call "HelloWorld.cpp" under src as well, then put the same content like cli in OS X.












but remember we need to reference the jni.h path ?, here you add includes via right click project and select properties. 

The reference path can be as below.

















Double check , whether you "check" - "Shared"
























Then you set up run configuration as below, especially double check your C++ Application: (Path).






















Last, one of the key is select "-fPIC", this is the key when you compiler in Linux.





















After you right click build the project, you should be able see there has new *.so shared library under debug folder.

















PS: if you got the required C++11 error, you can add compiler parameter as below.












Java Project reference C++ *.so


Then, back to your Java project, if you don't just create a java project then copy the HelloWorld.java we created before copy to the project.

But one of the key here is we would like to add *.so ( shared object ) in this project.

First of all we would like to double check what's the "Java Library Path". we can try the "System.out.println(System.getProperty("java.library.path"));"
















if you can't find the path where you local your *.so file, you can setup as below in your java environment.





Run java project in command line


If you want to run the java in command line, you might export the java project in to a Runnable jar - using Eclipse IDE. Select the main or running class file - Launch configuration. In Library handling - select the option [ Extract required libraries in to jar file ].

Open command prompt go to the directory where runnable jar is available and run 
#java -jar *.jar

BUT if you reference *.so ( C++ share objecdt ), you need to figure out the jni path as above command "System.out.println(System.getProperty("java.library.path"));", e.g.: /usr/lib. then copy your *.so to /usr/lib, otherwise your java code will be error out when you run share C++ library.

Again, this POC lab and Eclipse environment setup, I think you can complete it in 5 ~ 20 min, but be aware for the basic logic is more important to make the C++ work on Java.



Hope you enjoy it !

PS: Run Java with Error : out of memory.

If you ran into the out of memory error, which means your code might have memory leak or the java heap memory simply is not big enough for caching the object. 

For the memory leak, remember garbage collection in your code.

For java heap is not big enough, you can enlarge via the link as below.

http://www.mkyong.com/eclipse/eclipse-java-lang-outofmemoryerror-java-heap-space/

Reference:

http://mrjoelkemp.com/2012/01/getting-started-with-jni-and-c-on-osx-lion/

If you gave your best, any outcome will be perfect in its own way. - (Whiplash), 2014


4 comments:

  1. Hi can i consume the c++ library from java jersey restful service? how to consume? can you help me?

    ReplyDelete
    Replies
    1. I think you might need to prepare a java class wrap c++ library and compose a RESTful API as interface jersey for it.

      Delete
  2. The Spring Framework is a lightweight framework for developing Java enterprise applications. It provides high performing, easily testable and reusable code. Spring handles the infrastructure as the underlying framework so that you can focus on your application.Spring is modular in design, thereby making creation, handling and linking of individual components so much easier. Spring implements Model View Container(MVC) design pattern.
    spring custom validator example

    ReplyDelete