JNI: объединяем C и Java

Что такое JNI?

Java Native Interface (JNI) — стандартный механизм для запуска кода, который написан на языках С/С++ или Ассемблера, и скомпонован в виде динамических библиотек, позволяет не использовать статическое связывание. Это даёт возможность вызова функции С/С++ из программы на Java, и наоборот.

Если говорить кратко, JNI - механизм, связывающий Java и C/C++ в одно целое.

Как пользоваться: Java

Со стороны Java нам нужно описать класс, загружающий динамическую библиотеку и имеющий в себе описание функций, расположенных в библиотеке:

Sample code
1
2
3
4
5
6
System.loadLibrary("Library"); // Будет загружать Library.o или Library.dll в зависимости от ОС
...
// Данные функции объявлены с модификатором native
// Это значит, что они расположены в динамической библиотеке
public static native void libraryFunc();
public static native int libraryFunc(int x);

Как пользоваться: С

Следующим этапом необходимо создать заголовочный файл библиотеки для дальнейшей работы с динамической библиотекой:

Sample code
1
2
javac -g Main.java
javah -jni Main

После выполнения данных команд в терминале мы получим файл Main.h примерно такого содержания:

Sample code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Main */
#ifndef _Included_Main
#define _Included_Main
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Main
* Method: libraryFunc
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_Main_libraryFunc
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif

Как видим, в нашем заголовочном файле уже описана функция Java_Main_libraryFunc, которая расположена в классе Main и называется libraryFunc. В динамической библиотеке функция принимает дополнительные аргументы - окружение и класс.
Переходим к файлу Main.c, в котором опишем реализацию функции Java_Main_libraryFunc:

Sample code
1
2
3
4
5
6
7
#include <jni.h>
#include <stdio.h>
#include "Main.h"
JNIEXPORT void JNICALL Java_Main_libraryFunc(JNIEnv *env, jobject obj) {
printf("Native code executed!\n");
}

Теперь этот небольшой модуль нужно скомпилировать в динамическую библиотеку:

Sample code
1
gcc -std=c99 -Wl,--add-stdcall-alias -I"<JAVA_HOME>\include" -I"<JAVA_HOME>\include\win32" -shared -o Library.dll

Также нужно иметь ввиду, что разрядность библиотеки должна быть такой же, как и разрядность JVM.

Связываем вместе

После того, как библиотека скомпилирована, достаточно загрузить ее и вызвать нужную функцию:

Sample code
1
2
System.loadLibrary("Library");
libraryFunc();

Заключение

JNI - отличное средство для более близкого взаимодействия с системой. Все, что нельзя сделать средствами чистой Java, можно сделать на С/С++ и вызвать из Java. Единственным вопросом остается производительность - в каких случаях стоит обратиться к нативному коду, а в каких достаточно воспользоваться стандартными средствами.

Ссылки