Geek Culture
Published in

Geek Culture

Photo by sigmund on Unsplash

A Simple Guide To Java Native Interface (JNI) Using Native Maven Plugin

macOS configuration

This configuration was tested with the latest Xcode on macOS Big Sur. Please note that the Native Maven Plugin requires command-line tools installed.

<profile>
<id>macOS</id>
<activation>
<os>
<family>mac</family>
</os>
</activation>
<properties>
<os_name>mac</os_name>
<!-- should follow the pattern lib*library_name*.dylib-->
<lib_name>libjnilibrary.dylib</lib_name>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>1.0-alpha-11</version>
<extensions>true</extensions>
<configuration>
<javahOS>${os_name}</javahOS>
<sources>
<source>
<!-- path to the C/C++ sources -->
<directory>src/native/source</directory>
<fileNames>
<fileName>jni.c</fileName>
</fileNames>
</source>
<!-- in this example - path to generated JNI header -->
<source>
<directory>src/native/include</directory>
</source>
</sources>
<compilerProvider>generic-classic</compilerProvider>
<compilerExecutable>gcc</compilerExecutable>
<!-- compiler options -->
<compilerStartOptions>
<compilerStartOption>-m64</compilerStartOption>
<compilerStartOption>-Wall</compilerStartOption>
<compilerStartOption>-Wextra</compilerStartOption>
<compilerStartOption>-O3</compilerStartOption>
<compilerStartOption>-I</compilerStartOption>
<compilerStartOption>
${env.JAVA_HOME}/include
</compilerStartOption>
<compilerStartOption>-I</compilerStartOption>
<compilerStartOption>
${env.JAVA_HOME}/include/darwin
</compilerStartOption>
</compilerStartOptions>
<linkerOutputDirectory>target</linkerOutputDirectory>
<linkerExecutable>gcc</linkerExecutable>
<!-- linker options -->
<linkerStartOptions>
<linkerStartOption>-m64</linkerStartOption>
<linkerStartOption>-shared</linkerStartOption>
</linkerStartOptions>
<linkerEndOptions>
<linkerEndOption>
-o ${project.build.directory}/${lib_name}
</linkerEndOption>
</linkerEndOptions>
</configuration>
<executions>
<execution>
<id>javah</id>
<phase>compile</phase>
<goals>
<goal>initialize</goal>
<goal>compile</goal>
<goal>link</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>

Windows 10 (Visual Studio 2017) configuration

Unfortunately, Native Maven Plugin does not provide environment factories for MSVC newer than MSVC2013, so I’ve created my own factory for MSVC2017x64. You need to clone this repository, build and install it using mvn install command.

<profile>
<id>windows</id>
<activation>
<os>
<family>windows</family>
</os>
</activation>
<properties>
<os_name>windows</os_name>
<!-- note that the libary name differs
from the macOS configuration-->
<lib_name>jnilibrary</lib_name>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>1.0-alpha-11</version>
<extensions>true</extensions>
<dependencies>
<dependency>
<groupId>com.edwardhyde</groupId>
<artifactId>vc-2017-support</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<configuration>
<javahOS>${os_name}</javahOS>
<compilerProvider>msvc</compilerProvider>
<!-- custom environment factory for MSVC2017-->
<envFactoryName>
com.edwardhyde.MSVC2017x64EnvFactory
</envFactoryName>
<sources>
<source>
<directory>src/native/source</directory>
<fileNames>
<fileName>jni.c</fileName>
</fileNames>
</source>
<source>
<directory>src/native/include</directory>
</source>
</sources>
<compilerProvider>msvc</compilerProvider>
<compilerStartOptions>
<!-- create DLL-->
<compilerStartOption>/LD</compilerStartOption>
<compilerStartOption>/MD</compilerStartOption>
<compilerStartOption>-I</compilerStartOption>
<compilerStartOption>
${env.JAVA_HOME}\include
</compilerStartOption>
<compilerStartOption>-I</compilerStartOption>
<compilerStartOption>
${env.JAVA_HOME}\include\win32
</compilerStartOption>
</compilerStartOptions>
<linkerOutputDirectory>target</linkerOutputDirectory>
<linkerProvider>msvc</linkerProvider>
<linkerFinalName>${lib_name}</linkerFinalName>
<linkerFinalNameExt>dll</linkerFinalNameExt>
<linkerStartOptions>
<linkerStartOption>/INCREMENTAL:NO</linkerStartOption>
<linkerStartOption>/DLL</linkerStartOption>
</linkerStartOptions>
</configuration>
<executions>
<execution>
<id>javah</id>
<phase>compile</phase>
<goals>
<goal>initialize</goal>
<goal>compile</goal>
<goal>link</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
package com.jni.example;

public class JniWrapper {
public native String getString();
}
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.5.0</version>
<executions>
<execution>
<id>generate-jni-headers</id>
<phase>generate-sources</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<workingDirectory>${project.basedir}</workingDirectory>
<executable>javah</executable>
<arguments>
<argument>-verbose</argument>
<argument>-jni</argument>
<argument>-classpath</argument>
<argument>${project.build.sourceDirectory}</argument>
<argument>-d</argument>
<!-- directory where generated header will be placed -->
<argument>
${project.basedir}/src/native/include
</argument>
<!-- target class-->
<argument>com.jni.example.JniWrapper</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
Project structure
  • JNIEnv a pointer to a structure storing all JNI function pointers.
  • jobject — a reference to the Java object that the method is attached to (the instance of our JniWrapper class).
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_jni_example_JniWrapper */

#ifndef _Included_com_jni_example_JniWrapper
#define _Included_com_jni_example_JniWrapper
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_jni_example_JniWrapper
* Method: getString
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL
Java_com_jni_example_JniWrapper_getString(JNIEnv *, jobject);


#ifdef __cplusplus
}
#endif
#endif
#include "com_jni_example_JniWrapper.h"
#include
<stdio.h>

#define STRING_RETURN "Hello world!"
#define
STRING_CALLBACK "Hello world callback!"
#define
STRING_CALLBACK_STATIC "Hello world static callback!"
#define
STRING_PAYLOAD "Simple payload string"

JNIEXPORT jstring JNICALL Java_com_jni_example_JniWrapper_getString(JNIEnv *env,
jobject thiz) {
// get the class of an object

jclass cls_foo = (*env)->GetObjectClass(env, thiz);
// callback instance method with one String parameter
// and void result
jmethodID callback = (*env)->GetMethodID(env,
cls_foo,
"callback",
"(Ljava/lang/String;)V");
// callbackObject instance method with one
// List<String> arrayList
parameter and String result
jmethodID callbackWithObject = (*env)->GetMethodID(env,
cls_foo,
"callbackObject",
"(Ljava/util/List;)Ljava/lang/String;");
// callback static method with one String parameter
// and void result
jmethodID callbackStatic = (*env)->GetStaticMethodID(env,
cls_foo,
"callbackStatic",
"(Ljava/lang/String;)V");
// construct a new java.lang.String objects from
// an array of characters in UTF-8 encoding

jstring jStringRegular = (*env)->NewStringUTF(env,
STRING_CALLBACK);

jstring jStringStatic = (*env)->NewStringUTF(env,
STRING_CALLBACK_STATIC);
// call instance method with void result
(*env)->CallVoidMethod(env, thiz, callback, jStringRegular);
// call static instance method with void result
(*env)->CallStaticVoidMethod(env,
thiz,

callbackStatic,
jStringStatic
);
// delete the local references
(*env)->DeleteLocalRef(env, jStringRegular);
(*env)->DeleteLocalRef(env, jStringStatic);

jclass cls = (*env)->FindClass(env, "java/util/ArrayList");
// get the default constructor for java.util.ArrayList class
jmethodID constructor = (*env)->GetMethodID(env,
cls,
"<init>"
,
"()V");
// create a new instance of java.util.ArrayList
jobject arraylist = (*env)->NewObject(env, cls, constructor);

jstring jStringPayload = (*env)->NewStringUTF(env,
STRING_PAYLOAD
);
// get the add method
jmethodID addMethod = (*env)->GetMethodID(env,
cls,

"add",
"(Ljava/lang/Object;)Z");
// call the add method that returns boolean
(*env)->CallBooleanMethod(env, arraylist,
addMethod, jStringPayload);
// call the callbackObject method that returns String
jobject resultString = (*env)->CallObjectMethod(env,
thiz,
callbackWithObject,
arraylist);
// convert the jobject to an array of characters
const char* str = (*env)->GetStringUTFChars(env,
(jstring) resultString,
NULL);
// print returned string
printf(str);
(*env)->ReleaseStringUTFChars(env, resultString, str); (*env)->DeleteLocalRef(env, jStringPayload); (*env)->DeleteLocalRef(env, arraylist);

return (*env)->NewStringUTF(env, STRING_RETURN);
}
package com.jni.example;

import java.util.List;

@SuppressWarnings("unused")
public class JniWrapper {
private static final String LIB_NAME = "jnilibrary";

static {
// load the native library
System.loadLibrary(LIB_NAME);
}

public static void callbackStatic(String string) {
System.out.println("Static callback: " + string);
}

public native String getString();

public void callback(String string) {
System.out.println("Callback: " + string);
}

public String callbackObject(List<String> arrayList) {
System.out.println("Callback object with size: "
+ arrayList.size()
+ " and payload: "
+ arrayList.get(0));
return arrayList.get(0);
}

public static void main(String[] args) {
JniWrapper jni = new JniWrapper();
System.out.println(jni.getString());
}
}
Execution results

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store