Skip to content

A simple documentation on JNI, to explain its functionality step by step

Notifications You must be signed in to change notification settings

Cestaro0/How-To-Use-JNI

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 

Repository files navigation

How to Use JNI

Imagine you are a developer who needs to use a C++ DLL within Java; JNI makes this possible. I'm creating this documentation to simplify the process since I had some difficulties understanding it myself. I hope to help you understand. We will cover the following topics:

  • What is JNI
  • How to use JNI step by step

We will use IntelliJ and Visual Studio for this.

Configure Java

First, to get started with everything we need, download the Java Development Kit (JDK) for Java and configure your machine accordingly. Reminder: If your application is 64-bit, download the x64 version; if it's 32-bit, you'll need an older version since Java stopped creating 32-bit versions after Java 8. Access the JDK 11 download link, download it, and it will automatically be configured in your environment variables. If not, follow these steps: Open the terminal and type:

C:\Users>setx JAVA_HOME "C:\Program Files\Java\jdk-xx.x.x"
C:\Users>setx PATH "%JAVA_HOME%\bin"

Now it's configured, and you should receive this message:

C:\Users>java --version
java 11.0.16.1 2022-08-18 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.16.1+1-LTS-1)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.16.1+1-LTS-1, mixed mode)

JNI

Now, let's talk about JNI (Java Native Interface). To put it simply, it's a way that Oracle created for Java to communicate with C++. There's also JNA, maintained by a GitHub community (but we won't discuss it here). Below is an example image: Here, you can see that JNI acts as an intermediary between the JVM and the C++ .dll where it runs the Java code, which in turn calls C++ functions. The .dll is created with a mix of Java and C++ in a JNI header.

Create the Java Project

First, download the IntelliJ platform. After that, create a project; it can be IntelliJ, Gradle, or Maven, it doesn't matter. Here's an example Java code snippet:

public class CallCppFromJava {

    public static native String sayWrapperHello();
    public static native String sayCppDllHello(String str);
    public static native int sumWrapper(int a, int b);
    public static native int sumCppDll(int a, int b);

    public static void main(String[] args) {

    }
}

Here, we've created four methods: two of them will use our JNI DLL, which we'll call the "wrapper" from now on, and the other two will be returned by the native C++ DLL. Don't forget to generate the .h header (typed in the terminal); it's a key component for us.

C++

Create a native C++ DLL using Visual Studio:

#include "pch.h"

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

extern "C" __declspec (dllexport) const char* sayCppHello(const char* str)
{
    return str;
}

extern "C" __declspec (dllexport) int sumCppDll(int a, int b)
{
    return a + b;
}

Now, let's create the wrapper. Create another native C++ DLL, but this time add the .h generated from Java:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
#include <iostream>
#include <Windows.h>
/* Header for class CallCppFromJava */

#ifndef _Included_CallCppFromJava
#define _Included_CallCppFromJava
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     CallCppFromJava
 * Method:    sayWrapperHello
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_CallCppFromJava_sayWrapperHello
  (JNIEnv *, jclass);

/*
 * Class:     CallCppFromJava
 * Method:    sayCppDllHello
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_CallCppFromJava_sayCppDllHello
  (JNIEnv *, jclass, jstring);

/*
 * Class:     CallCppFromJava
 * Method:    sumWrapper
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_CallCppFromJava_sumWrapper
  (JNIEnv *, jclass, jint, jint);

/*
 * Class:     CallCppFromJava
 * Method:    sumCppDll
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_CallCppFromJava_sumCppDll
  (JNIEnv *, jclass, jint, jint);

#ifdef __cplusplus
}
#endif
#endif

Now, let's create the .cc file that will manage everything. But before that, go to:
Properties > C/C++ > Additional Include Directories and add "C:\Program Files\Java\jdk-11.0.16.1\include" and "C:\Program Files\Java\jdk-11.0.16.1\include\win32", disable precompiled header.

and click Apply. Your .cc file should look like this:

#include "CallCppFromJava.h"

extern "C"
{
	JNIEXPORT jstring JNICALL Java_CallCppFromJava_sayWrapperHello(JNIEnv* env, jclass cls)
	{
		return env->NewStringUTF("Hello From Wrapper");
	}

	JNIEXPORT jstring JNICALL Java_CallCppFromJava_sayCppDllHello(JNIEnv* env, jclass cls, jstring str)
	{
		const char* cppStr = env->GetStringUTFChars(str, JNI_FALSE);
		HMODULE hModule = LoadLibraryA("D:\\DEV\\JNI\\Dll2\\x64\\Release\\Dll2.dll");
		typedef const char* (*sayCppHello)(const char*);
		sayCppHello hello = (sayCppHello)GetProcAddress(hModule, "sayCppHello");
		return env->NewStringUTF(hello(cppStr));
	}

	JNIEXPORT jint JNICALL Java_CallCppFromJava_sumWrapper(JNIEnv* env, jclass cls, jint a, jint b)
	{
		return a + b;
	}

	JNIEXPORT jint JNICALL Java_CallCppFromJava_sumCppDll(JNIEnv* env, jclass cls, jint a, jint b)
	{
		HMODULE hModule = LoadLibraryA("D:\\DEV\\JNI\\Dll2\\x64\\Release\\Dll2.dll");
		typedef int (*sumCppDll)(int, int);
		sumCppDll sum = (sumCppDll)GetProcAddress(hModule, "sumCppDll");
		return sum(a,b);
	}
}

Now, compile for your architecture and

Calling C++ Functions

Returning to Java, let's simply make calls to the functions:

public class CallCppFromJava {
    static{
        System.load("D:\\DEV\\JNI\\Dll3\\x64\\Release\\Dll3.dll");
    }

    public static native String sayWrapperHello();
    public static native String sayCppDllHello(String str);
    public static native int sumWrapper(int a, int b);
    public static native int sumCppDll(int a, int b);

    public static void main(String[] args) {
        System.out.println("sayWrapperHello: " + sayWrapperHello());
        System.out.println("sayCppDllHello: " + sayCppDllHello("Hello"));
        System.out.println("sumWrapper: " + sumWrapper(1, 2));
        System.out.println("sumCppDll: " + sumCppDll(1, 2));
    }
}

output:

sayWrapperHello: Hello From Wrapper
sayCppDllHello: Hello
sumWrapper: 3
sumCppDll: 3

About

A simple documentation on JNI, to explain its functionality step by step

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published