Skip to content

Calling native functions from Java with JNI and Maven

by Matthias Fraass on Februar 3rd, 2011

Building JNI modules is a tricky thing – having them managed by a good build system is even trickier.
So I’ve created working example which contains both the Java and the native code of a JNI implementation. I won’t talk about JNI itself as this is an old technology with lots of examples and documentation.

Let’s start with the simple things – the source files. In this first example, we’d like to call a native function from Java. So here’s the Java class:

package net.tricoder.jnitest;

public class NativeStuff
{
public native void helloNative();

public static void main(String[] args)
{
System.loadLibrary("jniExampleNative");
new NativeStuff().helloNative();
}
}
#include <stdio.h>
#include <stdlib.h>
#include <jni.h>
#include <nativeStuff.h> // generated by javah via maven-native-plugin

JNIEXPORT void JNICALL Java_net_tricoder_jnitest_NativeStuff_helloNative
  (JNIEnv * env, jobject obj)
{
    puts("Hello from C!");
}

You can build that quite quickly by hand:

  1. javac the .java file;
  2. javah the class so you get a header file;
  3. compile the C source,
  4. link the C source
  5. profit!

But as I said, we’d like to put this into a standard maven structure and automate these steps. Bonus poitns are awarded for multi platform support. Win32 and HP-UX will do for us.

There’s a Maven Plugin called native-maven-plugin. I found that the documentation and examples are too spares (and sometimes outdated).

A picture tells a thousand words, so I’ll just put the structure here to glance at:

Maven project structure

I will explain the Java part bottom-up and the C part top-down.

The Java part

The Java source file and Java build file are straightforward. This is the pom.xml for the Java part:

    <project>
  <modelVersion>4.0.0</modelVersion>

  <parent>
    <groupId>net.tricoder.jnitest</groupId>
    <artifactId>parentProject</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>

  <groupId>net.tricoder.jnitest</groupId>
  <artifactId>jniExampleJava</artifactId>
  <name>JNI example - Java </name>

  <build>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </plugin>  
    </plugins>
  </build>
</project>

And this is called from the main pom.xml (the lowest one in the picture above):

<project>

  <modelVersion>4.0.0</modelVersion>
  <groupId>net.tricoder.jnitest</groupId>
  <artifactId>parentProject</artifactId>
  <version>1.0-SNAPSHOT</version>
  <name>JNI example parent project</name>
 
  <packaging>pom</packaging>
  <modules>
    <module>java</module>
    <module>native</module>
  </modules>

  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.codehaus.mojo</groupId>
          <artifactId>native-maven-plugin</artifactId>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
 
</project>

As you can see, this is included as a module “java”.
The other module is called “native”.

The C part

The “native” module is represented by the pom.xml in the native/ sub folder. This is the switch for the platform – in our case win32 or HP-UX:

<project>

  <modelVersion>4.0.0</modelVersion>

  <parent>
    <groupId>net.tricoder.jnitest</groupId>
    <artifactId>parentProject</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>

  <artifactId>nativeParent</artifactId>
 
  <name>JNI example - native parent</name>
 
  <packaging>pom</packaging>

  <profiles>
 
    <profile>
      <id>win32</id>
      <activation>
        <property>
          <name>platform</name>
          <value>win32</value>
        </property>
      </activation>
      <modules>
        <module>win32</module>
      </modules>
    </profile>

    <profile>
      <id>hpux</id>
      <activation>
        <property>
          <name>platform</name>
          <value>hpux</value>
        </property>
      </activation>
      <modules>
        <module>hpux</module>
      </modules>
    </profile>

  </profiles>

</project>

Win32

The Win32 artifact is created by its own pom.xml in the native/win32 sub folder. I will explain that file bit by bit. I’m using MinGW, by the way.
First, theres the standard artifact definition with parents and packaging. As we’re creating a library, this will be “dll” for Windows:

<project>
  <modelVersion>4.0.0</modelVersion>
   
  <parent>
    <groupId>net.tricoder.jnitest</groupId>
    <artifactId>nativeParent</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>
   
    <groupId>net.tricoder.jnitest</groupId>
    <artifactId>jniExampleNative</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>JNI example native win32</name>
    <url>http://maven.apache.org</url>
   
    <packaging>dll</packaging>

Then we have a dependecy to the Java artifact. Why? Well, see Step #2 above: we have to call javah on the class file in order to generate the matching header file.

  <dependencies>
    <dependency>
      <groupId>net.tricoder.jnitest</groupId>
      <artifactId>jniExampleJava</artifactId>
      <version>1.0-SNAPSHOT</version>
      <type>jar</type>
      <scope>compile</scope>
    </dependency>  
  </dependencies>

This is followed by the build part, which mainly consists of the compiler and linker configuration:

    <build>
        <plugins>
       
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>native-maven-plugin</artifactId>
                <extensions>true</extensions>
                <configuration>
                  <!--  trigger javah -->
                  <javahOS>win32</javahOS>
         
                  <compilerProvider>generic-classic</compilerProvider>
                    <compilerExecutable>gcc</compilerExecutable>
                    <linkerExecutable>gcc</linkerExecutable>
                    <sources>
                        <source>
                            <directory>../src/main/native</directory>
                            <fileNames>
                                <fileName>jni_example.c</fileName>
                            </fileNames>
                        </source>
                    </sources>

                    <linkerStartOptions>
                     <linkerStartOption>-shared -mno-cygwin -Wl,--add-stdcall-alias</linkerStartOption>
                    </linkerStartOptions>

                </configuration>

Note the subtle

      <!--  trigger javah -->
          <javahOS>win32</javahOS>

which is crucial.

The pom file ends width the javah configuration:

        <executions>
           <execution>
             <id>javah</id>
             <phase>generate-sources</phase>
             <configuration>
          <javahOS>win32</javahOS>
          <javahProvider>default</javahProvider>
          <javahOutputDirectory>${project.build.directory}/custom-javah</javahOutputDirectory>
          <workingDirectory>${basedir}</workingDirectory>
          <javahOutputFileName>nativeStuff.h</javahOutputFileName>
          <javahClassNames>
            <javahClassName>net.tricoder.jnitest.NativeStuff</javahClassName>
          </javahClassNames>
             </configuration>
             <goals>
               <goal>javah</goal>
             </goals>
           </execution>
        </executions>              
            </plugin>
        </plugins>
    </build>
</project>

hpux

We have a similar pom.xml for HP-UX, which should be self explanatory by now. It only differs in compiler and linker options, of course.

<project>
  <modelVersion>4.0.0</modelVersion>
 
  <parent>
    <groupId>net.tricoder.jnitest</groupId>
    <artifactId>nativeParent</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>
   
  <groupId>net.tricoder.jnitest</groupId>
  <artifactId>jniExampleNative</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>JNI example native HP-UX</name>
  <url>http://maven.apache.org</url>
   
    <packaging>so</packaging>

  <dependencies>
    <dependency>
      <groupId>net.tricoder.jnitest</groupId>
      <artifactId>jniExampleJava</artifactId>
      <version>1.0-SNAPSHOT</version>
      <type>jar</type>
      <scope>compile</scope>
    </dependency>  
  </dependencies>
 
    <build>
        <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </plugin>    
       
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>native-maven-plugin</artifactId>
                <extensions>true</extensions>
                <configuration>
          <!--  trigger javah -->          
          <javahOS>hp-ux</javahOS>
         
          <compilerProvider>generic-classic</compilerProvider>
                    <compilerExecutable>cc</compilerExecutable>
                   
                    <linkerExecutable>cc</linkerExecutable>
                    <sources>
                        <source>
                            <directory>../src/main/native</directory>
                            <fileNames>
                                <fileName>jni_example.c</fileName>
                            </fileNames>
                        </source>
                    </sources>
                   
                    <compilerStartOptions>
                     <compilerStartOption>+z +u4 -c -mt</compilerStartOption>
                    </compilerStartOptions>

                    <linkerStartOptions>
           <linkerStartOption>-b -lstd -lstream -lCsup -lunwind -lm</linkerStartOption>
                    </linkerStartOptions>

                </configuration>
               
                <executions>
           <execution>
             <id>javah</id>
             <phase>generate-sources</phase>
             <configuration>
          <javahOS>hp-ux</javahOS>
          <javahProvider>default</javahProvider>
          <javahOutputDirectory>${project.build.directory}/custom-javah</javahOutputDirectory>
          <workingDirectory>${basedir}</workingDirectory>
          <javahOutputFileName>nativeStuff.h</javahOutputFileName>
          <javahClassNames>
            <javahClassName>net.tricoder.jnitest.NativeStuff</javahClassName>
          </javahClassNames>
             </configuration>
             <goals>
               <goal>javah</goal>
             </goals>
           </execution>
        </executions>      
               
            </plugin>
        </plugins>
    </build>
</project>

Building

Calling “mvn clean install -P win32″ on the root module will start the build proces:

[INFO] Scanning for projects...
[INFO] Reactor build order:
[INFO]   JNI example parent project
[INFO]   JNI example - Java
[INFO]   JNI example - native parent
[INFO]   JNI example native win32
...
[INFO] ------------------------------------------------------------------------
[INFO] Building JNI example parent project
[INFO]    task-segment: [clean, install]
[INFO] ------------------------------------------------------------------------
[INFO] [clean:clean {execution: default-clean}]
[INFO] [site:attach-descriptor {execution: default-attach-descriptor}]
[INFO] [install:install {execution: default-install}]
[INFO] Installing C:\dev\jniMavenExample\pom.xml to C:\dev\apache-maven-2.2.1\repository\net\tricoder\jnite
st\parentProject\1.0-SNAPSHOT\parentProject-1.0-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] Building JNI example - Java
[INFO]    task-segment: [clean, install]
[INFO] ------------------------------------------------------------------------
[INFO] [clean:clean {execution: default-clean}]
[INFO] Deleting directory C:\dev\jniMavenExample\java\target
[INFO] [resources:resources {execution: default-resources}]
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory C:\dev\jniMavenExample\java\src\main\resources
[INFO] [compiler:compile {execution: default-compile}]
[INFO] Compiling 1 source file to C:\dev\jniMavenExample\java\target\classes
[INFO] [resources:testResources {execution: default-testResources}]
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory C:\dev\jniMavenExample\java\src\test\resources
[INFO] [compiler:testCompile {execution: default-testCompile}]
[INFO] No sources to compile
[INFO] [surefire:test {execution: default-test}]
[INFO] No tests to run.
[INFO] [jar:jar {execution: default-jar}]
[INFO] Building jar: C:\dev\jniMavenExample\java\target\jniExampleJava-1.0-SNAPSHOT.jar
[INFO] [install:install {execution: default-install}]
[INFO] Installing C:\dev\jniMavenExample\java\target\jniExampleJava-1.0-SNAPSHOT.jar to C:\dev\apache-maven
-2.2.1\repository\net\tricoder\jnitest\jniExampleJava\1.0-SNAPSHOT\jniExampleJava-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] Building JNI example - native parent
[INFO]    task-segment: [clean, install]
[INFO] ------------------------------------------------------------------------
[INFO] [clean:clean {execution: default-clean}]
[INFO] [site:attach-descriptor {execution: default-attach-descriptor}]
[INFO] [install:install {execution: default-install}]
[INFO] Installing C:\dev\jniMavenExample\native\pom.xml to C:\dev\apache-maven-2.2.1\repository\net\tricode
r\jnitest\nativeParent\1.0-SNAPSHOT\nativeParent-1.0-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] Building JNI example native win32
[INFO]    task-segment: [clean, install]
[INFO] ------------------------------------------------------------------------
[INFO] [clean:clean {execution: default-clean}]
[INFO] Deleting directory C:\dev\jniMavenExample\native\win32\target
[INFO] [native:initialize {execution: default-initialize}]
[INFO] [native:unzipinc {execution: default-unzipinc}]
[INFO] [native:javah {execution: default-javah}]
[INFO] [native:javah {execution: javah}]
[INFO] cmd.exe /X /C "javah -o C:\dev\jniMavenExample\native\win32\target\custom-javah\nativeStuff.h -class
path C:\dev\jniMavenExample\native\win32\target\classes;C:\dev\jniMavenExample\java\target\jni
ExampleJava-1.0-SNAPSHOT.jar net.tricoder.jnitest.NativeStuff"
[INFO] [native:compile {execution: default-compile}]
[INFO] cmd.exe /X /C "gcc -IC:\dev\jniMavenExample\native\src\main\native -IC:\dev\jniMavenExa
mple\native\win32\target\custom-javah -Ic:\Programme\Java\jdk1.6.0_20\jre\..\include -Ic:\Programme\Java\jdk1.6.0_20\jre
\..\include\win32 -oC:\dev\jniMavenExample\native\win32\target\objs\jni_example.obj -c C:\dev\
jniMavenExample\native\src\main\native\jni_example.c"
[INFO] [native:link {execution: default-link}]
[INFO] cmd.exe /X /C "gcc -shared -mno-cygwin -Wl,--add-stdcall-alias -oC:\dev\jniMavenExample\native\win32
\target\jniExampleNative.dll target\objs\jni_example.obj"
[INFO] [native:manifest {execution: default-manifest}]
[INFO] [resources:testResources {execution: default-testResources}]
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory C:\dev\jniMavenExample\native\win32\src\test\resources
[INFO] [compiler:testCompile {execution: default-testCompile}]
[INFO] No sources to compile
[INFO] [surefire:test {execution: default-test}]
[INFO] No tests to run.
[INFO] [native:inczip {execution: default-inczip}]
[INFO] [install:install {execution: default-install}]
[INFO] Installing C:\dev\jniMavenExample\native\win32\target\jniExampleNative.dll to C:\dev\apache-maven-2.
2.1\repository\net\tricoder\jnitest\jniExampleNative\0.0.1-SNAPSHOT\jniExampleNative-0.0.1-SNAPSHOT.dll
[INFO]
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] ------------------------------------------------------------------------
[INFO] JNI example parent project ............................ SUCCESS [1.281s]
[INFO] JNI example - Java .................................... SUCCESS [6.000s]
[INFO] JNI example - native parent ........................... SUCCESS [0.016s]
[INFO] JNI example native win32 .............................. SUCCESS [0.875s]
[INFO] ------------------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 10 seconds
[INFO] Finished at: Thu Feb 03 22:34:00 CET 2011
[INFO] Final Memory: 18M/45M
[INFO] ------------------------------------------------------------------------

This will result in two artifacts: The Java JAR and the DLL.
Put them into a single folder and you will be able to exeute the Java class:

>java -cp jniExampleJava.jar net.tricoder.jnitest.NativeStuff
Hello from C!
>

And the best thing is: it all works under HP/UX in exactly the same way!

You can grab the whole project here. Now go, try it out and tell me what I could improve.

So in this HowTo I’ve explained how to create a build process for modules which call native functions form Java. I’ll try to put up another example to show you how to call methods of Java classes from native code.

So stay tuned ;) .

From → Coding

3 Comments
  1. Aivar Grislis permalink

    Thank you for this information, it’s just what I was looking for!

    I got the sample code win32 version to compile under 64-bit Ubuntu 10.10 and just tested it successfully under 32-bit Vista Business.

    1) I had to change the ‘compilerExecutable’ and ‘linkerExecutable’ tags in the win32 pom.xml from ‘gcc’ to ‘i586-minw32msvc-gcc’ to invoke the appropriate cross-compiler version of gcc. Otherwise I got an “unrecognized option” error running ld for “–add-stdcall-alias”.

    2) For others not that familiar with Ubuntu: to set up the build environment, after a fresh install of 10.10 I did “sudo apt-get install” to get the packages openjdk-6-jdk, mingw32, and then maven2.

  2. Meenu permalink

    Awesome man Thanks a ton I badly needed this thing

Trackbacks & Pingbacks

  1. Tweets that mention Calling native functions from Java with JNI and Maven | Tricoder Blog -- Topsy.com

Leave a Reply

Note: XHTML is allowed. Your email address will never be published.

Subscribe to this comment feed via RSS